blob: 44bdef54892c879a87b985edb686432f44dd1633 [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);
924 numValues++;
925 } else {
926 values.put(entry.column, (String) null);
927 mResolver.update(entry.uri, values, null, null);
928 }
929 } else {
930 if (!empty) {
931 values.clear();
932 entry.toValues(values);
933 if (entry.id != 0) {
934 mResolver.update(entry.uri, values, null, null);
935 } else {
936 mResolver.insert(entry.uri, values);
937 }
938 numValues++;
939 } else if (entry.id != 0) {
940 mResolver.delete(entry.uri, null, null);
941 }
942 }
943 }
944
945 if (numValues == 0) {
946 // The contact is completely empty, delete it
947 mResolver.delete(mUri, null, null);
948 mUri = null;
949 setResult(RESULT_CANCELED);
950 } else {
951 // Add the entry to the my contacts group if it isn't there already
952 People.addToMyContactsGroup(mResolver, ContentUris.parseId(mUri));
953 setResult(RESULT_OK, new Intent().setData(mUri));
954
955 // Only notify user if we actually changed contact
956 if (mContactChanged || mPhotoChanged) {
957 Toast.makeText(this, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
958 }
959 }
960 }
961
962 /**
963 * Takes the entered data and saves it to a new contact.
964 */
965 private void create() {
966 ContentValues values = new ContentValues();
967 String data;
968 int numValues = 0;
969
970 // Create the contact itself
971 final String name = mNameView.getText().toString();
972 if (name != null && TextUtils.isGraphic(name)) {
973 numValues++;
974 }
975 values.put(People.NAME, name);
976 values.put(People.PHONETIC_NAME, mPhoneticNameView.getText().toString());
977
978 // Add the contact to the My Contacts group
979 Uri contactUri = People.createPersonInMyContactsGroup(mResolver, values);
980
981 // Add the contact to the group that is being displayed in the contact list
982 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
983 int displayType = prefs.getInt(ContactsListActivity.PREF_DISPLAY_TYPE,
984 ContactsListActivity.DISPLAY_TYPE_UNKNOWN);
985 if (displayType == ContactsListActivity.DISPLAY_TYPE_USER_GROUP) {
986 String displayGroup = prefs.getString(ContactsListActivity.PREF_DISPLAY_INFO,
987 null);
988 if (!TextUtils.isEmpty(displayGroup)) {
989 People.addToGroup(mResolver, ContentUris.parseId(contactUri), displayGroup);
990 }
991 } else {
992 // Check to see if we're not syncing everything and if so if My Contacts is synced.
993 // If it isn't then the created contact can end up not in any groups that are
994 // currently synced and end up getting removed from the phone, which is really bad.
995 boolean syncingEverything = !"0".equals(Contacts.Settings.getSetting(mResolver, null,
996 Contacts.Settings.SYNC_EVERYTHING));
997 if (!syncingEverything) {
998 boolean syncingMyContacts = false;
999 Cursor c = mResolver.query(Groups.CONTENT_URI, new String[] { Groups.SHOULD_SYNC },
1000 Groups.SYSTEM_ID + "=?", new String[] { Groups.GROUP_MY_CONTACTS }, null);
1001 if (c != null) {
1002 try {
1003 if (c.moveToFirst()) {
1004 syncingMyContacts = !"0".equals(c.getString(0));
1005 }
1006 } finally {
1007 c.close();
1008 }
1009 }
1010
1011 if (!syncingMyContacts) {
1012 // Not syncing My Contacts, so find a group that is being synced and stick
1013 // the contact in there. We sort the list so at least all contacts
1014 // will appear in the same group.
1015 c = mResolver.query(Groups.CONTENT_URI, new String[] { Groups._ID },
1016 Groups.SHOULD_SYNC + "!=0", null, Groups.DEFAULT_SORT_ORDER);
1017 if (c != null) {
1018 try {
1019 if (c.moveToFirst()) {
1020 People.addToGroup(mResolver, ContentUris.parseId(contactUri),
1021 c.getLong(0));
1022 }
1023 } finally {
1024 c.close();
1025 }
1026 }
1027 }
1028 }
1029 }
1030
1031 // Handle the photo
1032 if (mPhoto != null) {
1033 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1034 mPhoto.compress(Bitmap.CompressFormat.JPEG, 75, stream);
1035 Contacts.People.setPhotoData(getContentResolver(), contactUri, stream.toByteArray());
1036 }
1037
1038 // Create the contact methods
1039 int entryCount = ContactEntryAdapter.countEntries(mSections, false);
1040 for (int i = 0; i < entryCount; i++) {
1041 EditEntry entry = ContactEntryAdapter.getEntry(mSections, i, false);
1042 if (entry.kind != EditEntry.KIND_CONTACT) {
1043 values.clear();
1044 if (entry.toValues(values)) {
1045 // Only create the entry if there is data
1046 entry.uri = mResolver.insert(
1047 Uri.withAppendedPath(contactUri, entry.contentDirectory), values);
1048 entry.id = ContentUris.parseId(entry.uri);
1049 numValues++;
1050 }
1051 } else {
1052 // Update the contact with any straggling data, like notes
1053 data = entry.getData();
1054 values.clear();
1055 if (data != null && TextUtils.isGraphic(data)) {
1056 values.put(entry.column, data);
1057 mResolver.update(contactUri, values, null, null);
1058 numValues++;
1059 }
1060 }
1061 }
1062
1063 if (numValues == 0) {
1064 mResolver.delete(contactUri, null, null);
1065 setResult(RESULT_CANCELED);
1066 } else {
1067 mUri = contactUri;
1068 Intent resultIntent = new Intent()
1069 .setData(mUri)
1070 .putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
1071 setResult(RESULT_OK, resultIntent);
1072 Toast.makeText(this, R.string.contactCreatedToast, Toast.LENGTH_SHORT).show();
1073 }
1074 }
1075
1076 /**
1077 * Build up the entries to display on the screen.
1078 *
1079 * @param extras the extras used to start this activity, may be null
1080 */
1081 private void buildEntriesForEdit(Bundle extras) {
1082 Cursor personCursor = mResolver.query(mUri, CONTACT_PROJECTION, null, null, null);
1083 if (personCursor == null) {
1084 Log.e(TAG, "invalid contact uri: " + mUri);
1085 finish();
1086 return;
1087 } else if (!personCursor.moveToFirst()) {
1088 Log.e(TAG, "invalid contact uri: " + mUri);
1089 finish();
1090 personCursor.close();
1091 return;
1092 }
1093
1094 // Clear out the old entries
1095 int numSections = mSections.size();
1096 for (int i = 0; i < numSections; i++) {
1097 mSections.get(i).clear();
1098 }
1099
1100 EditEntry entry;
1101
1102 // Name
1103 mNameView.setText(personCursor.getString(CONTACT_NAME_COLUMN));
1104 mNameView.addTextChangedListener(this);
1105
1106 // Photo
1107 mPhoto = People.loadContactPhoto(this, mUri, 0, null);
1108 if (mPhoto == null) {
1109 setPhotoPresent(false);
1110 } else {
1111 setPhotoPresent(true);
1112 mPhotoImageView.setImageBitmap(mPhoto);
1113 }
1114
1115 // Organizations
1116 Uri organizationsUri = Uri.withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY);
1117 Cursor organizationsCursor = mResolver.query(organizationsUri, ORGANIZATIONS_PROJECTION,
1118 null, null, null);
1119
1120 if (organizationsCursor != null) {
1121 while (organizationsCursor.moveToNext()) {
1122 int type = organizationsCursor.getInt(ORGANIZATIONS_TYPE_COLUMN);
1123 String label = organizationsCursor.getString(ORGANIZATIONS_LABEL_COLUMN);
1124 String company = organizationsCursor.getString(ORGANIZATIONS_COMPANY_COLUMN);
1125 String title = organizationsCursor.getString(ORGANIZATIONS_TITLE_COLUMN);
1126 long id = organizationsCursor.getLong(ORGANIZATIONS_ID_COLUMN);
1127 Uri uri = ContentUris.withAppendedId(Organizations.CONTENT_URI, id);
1128
1129 // Add an organization entry
1130 entry = EditEntry.newOrganizationEntry(this, label, type, company, title, uri, id);
1131 entry.isPrimary = organizationsCursor.getLong(ORGANIZATIONS_ISPRIMARY_COLUMN) != 0;
1132 mOrgEntries.add(entry);
1133 }
1134 organizationsCursor.close();
1135 }
1136
1137 // Notes
1138 if (!personCursor.isNull(CONTACT_NOTES_COLUMN)) {
1139 entry = EditEntry.newNotesEntry(this, personCursor.getString(CONTACT_NOTES_COLUMN),
1140 mUri);
1141 mNoteEntries.add(entry);
1142 }
1143
1144 // Ringtone
1145 entry = EditEntry.newRingtoneEntry(this,
1146 personCursor.getString(CONTACT_CUSTOM_RINGTONE_COLUMN), mUri);
1147 mOtherEntries.add(entry);
1148
1149 // Send to voicemail
1150 entry = EditEntry.newSendToVoicemailEntry(this,
1151 personCursor.getString(CONTACT_SEND_TO_VOICEMAIL_COLUMN), mUri);
1152 mOtherEntries.add(entry);
1153
1154 // Phonetic name
1155 mPhoneticNameView.setText(personCursor.getString(CONTACT_PHONETIC_NAME_COLUMN));
1156 mPhoneticNameView.addTextChangedListener(this);
1157
1158 personCursor.close();
1159
1160 // Build up the phone entries
1161 Uri phonesUri = Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY);
1162 Cursor phonesCursor = mResolver.query(phonesUri, PHONES_PROJECTION,
1163 null, null, null);
1164
1165 if (phonesCursor != null) {
1166 while (phonesCursor.moveToNext()) {
1167 int type = phonesCursor.getInt(PHONES_TYPE_COLUMN);
1168 String label = phonesCursor.getString(PHONES_LABEL_COLUMN);
1169 String number = phonesCursor.getString(PHONES_NUMBER_COLUMN);
1170 long id = phonesCursor.getLong(PHONES_ID_COLUMN);
1171 boolean isPrimary = phonesCursor.getLong(PHONES_ISPRIMARY_COLUMN) != 0;
1172 Uri uri = ContentUris.withAppendedId(phonesUri, id);
1173
1174 // Add a phone number entry
1175 entry = EditEntry.newPhoneEntry(this, label, type, number, uri, id);
1176 entry.isPrimary = isPrimary;
1177 mPhoneEntries.add(entry);
1178
1179 // Keep track of which primary types have been added
1180 if (type == Phones.TYPE_MOBILE) {
1181 mMobilePhoneAdded = true;
1182 }
1183 }
1184
1185 phonesCursor.close();
1186 }
1187
1188 // Build the contact method entries
1189 Uri methodsUri = Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY);
1190 Cursor methodsCursor = mResolver.query(methodsUri, METHODS_PROJECTION, null, null, null);
1191
1192 if (methodsCursor != null) {
1193 while (methodsCursor.moveToNext()) {
1194 int kind = methodsCursor.getInt(METHODS_KIND_COLUMN);
1195 String label = methodsCursor.getString(METHODS_LABEL_COLUMN);
1196 String data = methodsCursor.getString(METHODS_DATA_COLUMN);
1197 String auxData = methodsCursor.getString(METHODS_AUX_DATA_COLUMN);
1198 int type = methodsCursor.getInt(METHODS_TYPE_COLUMN);
1199 long id = methodsCursor.getLong(METHODS_ID_COLUMN);
1200 boolean isPrimary = methodsCursor.getLong(METHODS_ISPRIMARY_COLUMN) != 0;
1201 Uri uri = ContentUris.withAppendedId(methodsUri, id);
1202
1203 switch (kind) {
1204 case Contacts.KIND_EMAIL: {
1205 entry = EditEntry.newEmailEntry(this, label, type, data, uri, id);
1206 entry.isPrimary = isPrimary;
1207 mEmailEntries.add(entry);
1208
1209 if (isPrimary) {
1210 mPrimaryEmailAdded = true;
1211 }
1212 break;
1213 }
1214
1215 case Contacts.KIND_POSTAL: {
1216 entry = EditEntry.newPostalEntry(this, label, type, data, uri, id);
1217 entry.isPrimary = isPrimary;
1218 mPostalEntries.add(entry);
1219 break;
1220 }
1221
1222 case Contacts.KIND_IM: {
1223 Object protocolObj = ContactMethods.decodeImProtocol(auxData);
1224 if (protocolObj == null) {
1225 // Invalid IM protocol, log it then ignore.
1226 Log.e(TAG, "Couldn't decode IM protocol: " + auxData);
1227 continue;
1228 } else {
1229 if (protocolObj instanceof Number) {
1230 int protocol = ((Number) protocolObj).intValue();
1231 entry = EditEntry.newImEntry(this,
1232 getLabelsForKind(this, Contacts.KIND_IM)[protocol], protocol,
1233 data, uri, id);
1234 } else {
1235 entry = EditEntry.newImEntry(this, protocolObj.toString(), -1, data,
1236 uri, id);
1237 }
1238 mImEntries.add(entry);
1239 }
1240 break;
1241 }
1242 }
1243 }
1244
1245 methodsCursor.close();
1246 }
1247
1248 // Add values from the extras, if there are any
1249 if (extras != null) {
1250 addFromExtras(extras, phonesUri, methodsUri);
1251 }
1252
1253 // Add the base types if needed
1254 if (!mMobilePhoneAdded) {
1255 entry = EditEntry.newPhoneEntry(this,
1256 Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY),
1257 DEFAULT_PHONE_TYPE);
1258 mPhoneEntries.add(entry);
1259 }
1260
1261 if (!mPrimaryEmailAdded) {
1262 entry = EditEntry.newEmailEntry(this,
1263 Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
1264 DEFAULT_EMAIL_TYPE);
1265 entry.isPrimary = true;
1266 mEmailEntries.add(entry);
1267 }
1268
1269 mContactChanged = false;
1270 }
1271
1272 /**
1273 * Build the list of EditEntries for full mode insertions.
1274 *
1275 * @param extras the extras used to start this activity, may be null
1276 */
1277 private void buildEntriesForInsert(Bundle extras) {
1278 // Clear out the old entries
1279 int numSections = mSections.size();
1280 for (int i = 0; i < numSections; i++) {
1281 mSections.get(i).clear();
1282 }
1283
1284 EditEntry entry;
1285
1286 // Check the intent extras
1287 if (extras != null) {
1288 addFromExtras(extras, null, null);
1289 }
1290
1291 // Photo
1292 mPhotoImageView.setImageResource(R.drawable.ic_contact_picture);
1293
1294 // Add the base entries if they're not already present
1295 if (!mMobilePhoneAdded) {
1296 entry = EditEntry.newPhoneEntry(this, null, Phones.TYPE_MOBILE);
1297 entry.isPrimary = true;
1298 mPhoneEntries.add(entry);
1299 }
1300
1301 if (!mPrimaryEmailAdded) {
1302 entry = EditEntry.newEmailEntry(this, null, DEFAULT_EMAIL_TYPE);
1303 entry.isPrimary = true;
1304 mEmailEntries.add(entry);
1305 }
1306
1307 // Ringtone
1308 entry = EditEntry.newRingtoneEntry(this, null, mUri);
1309 mOtherEntries.add(entry);
1310
1311 // Send to voicemail
1312 entry = EditEntry.newSendToVoicemailEntry(this, "0", mUri);
1313 mOtherEntries.add(entry);
1314 }
1315
1316 private void addFromExtras(Bundle extras, Uri phonesUri, Uri methodsUri) {
1317 EditEntry entry;
1318
1319 // Read the name from the bundle
1320 CharSequence name = extras.getCharSequence(Insert.NAME);
1321 if (name != null && TextUtils.isGraphic(name)) {
1322 mNameView.setText(name);
1323 }
1324
1325 // Read the phonetic name from the bundle
1326 CharSequence phoneticName = extras.getCharSequence(Insert.PHONETIC_NAME);
1327 if (!TextUtils.isEmpty(phoneticName)) {
1328 mPhoneticNameView.setText(phoneticName);
1329 }
1330
1331 // Postal entries from extras
1332 CharSequence postal = extras.getCharSequence(Insert.POSTAL);
1333 int postalType = extras.getInt(Insert.POSTAL_TYPE, INVALID_TYPE);
1334 if (!TextUtils.isEmpty(postal) && postalType == INVALID_TYPE) {
1335 postalType = DEFAULT_POSTAL_TYPE;
1336 }
1337
1338 if (postalType != INVALID_TYPE) {
1339 entry = EditEntry.newPostalEntry(this, null, postalType, postal.toString(),
1340 methodsUri, 0);
1341 entry.isPrimary = extras.getBoolean(Insert.POSTAL_ISPRIMARY);
1342 mPostalEntries.add(entry);
1343 }
1344
1345 // Email entries from extras
1346 addEmailFromExtras(extras, methodsUri, Insert.EMAIL, Insert.EMAIL_TYPE,
1347 Insert.EMAIL_ISPRIMARY);
1348 addEmailFromExtras(extras, methodsUri, Insert.SECONDARY_EMAIL, Insert.SECONDARY_EMAIL_TYPE,
1349 null);
1350 addEmailFromExtras(extras, methodsUri, Insert.TERTIARY_EMAIL, Insert.TERTIARY_EMAIL_TYPE,
1351 null);
1352
1353 // Phone entries from extras
1354 addPhoneFromExtras(extras, phonesUri, Insert.PHONE, Insert.PHONE_TYPE,
1355 Insert.PHONE_ISPRIMARY);
1356 addPhoneFromExtras(extras, phonesUri, Insert.SECONDARY_PHONE, Insert.SECONDARY_PHONE_TYPE,
1357 null);
1358 addPhoneFromExtras(extras, phonesUri, Insert.TERTIARY_PHONE, Insert.TERTIARY_PHONE_TYPE,
1359 null);
1360
1361 // IM entries from extras
1362 CharSequence imHandle = extras.getCharSequence(Insert.IM_HANDLE);
1363 CharSequence imProtocol = extras.getCharSequence(Insert.IM_PROTOCOL);
1364
1365 if (imHandle != null && imProtocol != null) {
1366 Object protocolObj = ContactMethods.decodeImProtocol(imProtocol.toString());
1367 if (protocolObj instanceof Number) {
1368 int protocol = ((Number) protocolObj).intValue();
1369 entry = EditEntry.newImEntry(this,
1370 getLabelsForKind(this, Contacts.KIND_IM)[protocol], protocol,
1371 imHandle.toString(), null, 0);
1372 } else {
1373 entry = EditEntry.newImEntry(this, protocolObj.toString(), -1, imHandle.toString(),
1374 null, 0);
1375 }
1376 entry.isPrimary = extras.getBoolean(Insert.IM_ISPRIMARY);
1377 mImEntries.add(entry);
1378 }
1379 }
1380
1381 private void addEmailFromExtras(Bundle extras, Uri methodsUri, String emailField,
1382 String typeField, String primaryField) {
1383 CharSequence email = extras.getCharSequence(emailField);
1384
1385 // Correctly handle String in typeField as TYPE_CUSTOM
1386 int emailType = INVALID_TYPE;
1387 String customLabel = null;
1388 if(extras.get(typeField) instanceof String) {
1389 emailType = ContactMethods.TYPE_CUSTOM;
1390 customLabel = extras.getString(typeField);
1391 } else {
1392 emailType = extras.getInt(typeField, INVALID_TYPE);
1393 }
1394
1395 if (!TextUtils.isEmpty(email) && emailType == INVALID_TYPE) {
1396 emailType = DEFAULT_EMAIL_TYPE;
1397 mPrimaryEmailAdded = true;
1398 }
1399
1400 if (emailType != INVALID_TYPE) {
1401 EditEntry entry = EditEntry.newEmailEntry(this, customLabel, emailType, email.toString(),
1402 methodsUri, 0);
1403 entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
1404 mEmailEntries.add(entry);
1405
1406 // Keep track of which primary types have been added
1407 if (entry.isPrimary) {
1408 mPrimaryEmailAdded = true;
1409 }
1410 }
1411 }
1412
1413 private void addPhoneFromExtras(Bundle extras, Uri phonesUri, String phoneField,
1414 String typeField, String primaryField) {
1415 CharSequence phoneNumber = extras.getCharSequence(phoneField);
1416
1417 // Correctly handle String in typeField as TYPE_CUSTOM
1418 int phoneType = INVALID_TYPE;
1419 String customLabel = null;
1420 if(extras.get(typeField) instanceof String) {
1421 phoneType = Phones.TYPE_CUSTOM;
1422 customLabel = extras.getString(typeField);
1423 } else {
1424 phoneType = extras.getInt(typeField, INVALID_TYPE);
1425 }
1426
1427 if (!TextUtils.isEmpty(phoneNumber) && phoneType == INVALID_TYPE) {
1428 phoneType = DEFAULT_PHONE_TYPE;
1429 }
1430
1431 if (phoneType != INVALID_TYPE) {
1432 EditEntry entry = EditEntry.newPhoneEntry(this, customLabel, phoneType,
1433 phoneNumber.toString(), phonesUri, 0);
1434 entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
1435 mPhoneEntries.add(entry);
1436
1437 // Keep track of which primary types have been added
1438 if (phoneType == Phones.TYPE_MOBILE) {
1439 mMobilePhoneAdded = true;
1440 }
1441 }
1442 }
1443
1444 /**
1445 * Removes all existing views, builds new ones for all the entries, and adds them.
1446 */
1447 private void buildViews() {
1448 // Remove existing views
1449 final LinearLayout layout = mLayout;
1450 layout.removeAllViews();
1451
1452 buildViewsForSection(layout, mPhoneEntries,
1453 R.string.listSeparatorCallNumber_edit, SECTION_PHONES);
1454 buildViewsForSection(layout, mEmailEntries,
1455 R.string.listSeparatorSendEmail_edit, SECTION_EMAIL);
1456 buildViewsForSection(layout, mImEntries,
1457 R.string.listSeparatorSendIm_edit, SECTION_IM);
1458 buildViewsForSection(layout, mPostalEntries,
1459 R.string.listSeparatorMapAddress_edit, SECTION_POSTAL);
1460 buildViewsForSection(layout, mOrgEntries,
1461 R.string.listSeparatorOrganizations, SECTION_ORG);
1462 buildViewsForSection(layout, mNoteEntries,
1463 R.string.label_notes, SECTION_NOTE);
1464
1465 buildOtherViews(layout, mOtherEntries);
1466 }
1467
1468
1469 /**
1470 * Builds the views for a specific section.
1471 *
1472 * @param layout the container
1473 * @param section the section to build the views for
1474 */
1475 private void buildViewsForSection(final LinearLayout layout, ArrayList<EditEntry> section,
1476 int separatorResource, int sectionType) {
1477
1478 View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
1479 layout.addView(divider);
1480
1481 // Count up undeleted children
1482 int activeChildren = 0;
1483 for (int i = section.size() - 1; i >= 0; i--) {
1484 EditEntry entry = section.get(i);
1485 if (!entry.isDeleted) {
1486 activeChildren++;
1487 }
1488 }
1489
1490 // Build the correct group header based on undeleted children
1491 ViewGroup header;
1492 if (activeChildren == 0) {
1493 header = (ViewGroup) mInflater.inflate(R.layout.edit_separator_alone, layout, false);
1494 } else {
1495 header = (ViewGroup) mInflater.inflate(R.layout.edit_separator, layout, false);
1496 }
1497
1498 // Because we're emulating a ListView, we need to handle focus changes
1499 // with some additional logic.
1500 header.setOnFocusChangeListener(this);
1501
1502 TextView text = (TextView) header.findViewById(R.id.text);
1503 text.setText(getText(separatorResource));
1504
1505 // Force TextView to always default color if we have children. This makes sure
1506 // we don't change color when parent is pressed.
1507 if (activeChildren > 0) {
1508 ColorStateList stateList = text.getTextColors();
1509 text.setTextColor(stateList.getDefaultColor());
1510 }
1511
1512 View addView = header.findViewById(R.id.separator);
1513 addView.setTag(Integer.valueOf(sectionType));
1514 addView.setOnClickListener(this);
1515
1516 // Build views for the current section
1517 for (EditEntry entry : section) {
1518 entry.activity = this; // this could be null from when the state is restored
1519 if (!entry.isDeleted) {
1520 View view = buildViewForEntry(entry);
1521 header.addView(view);
1522 }
1523 }
1524
1525 layout.addView(header);
1526 }
1527
1528 private void buildOtherViews(final LinearLayout layout, ArrayList<EditEntry> section) {
1529 // Build views for the current section, putting a divider between each one
1530 for (EditEntry entry : section) {
1531 View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
1532 layout.addView(divider);
1533
1534 entry.activity = this; // this could be null from when the state is restored
1535 View view = buildViewForEntry(entry);
1536 view.setOnClickListener(this);
1537 layout.addView(view);
1538 }
1539
1540 View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
1541 layout.addView(divider);
1542 }
1543
1544 /**
1545 * Builds a view to display an EditEntry.
1546 *
1547 * @param entry the entry to display
1548 * @return a view that will display the given entry
1549 */
1550 /* package */ View buildViewForEntry(final EditEntry entry) {
1551 // Look for any existing entered text, and save it if found
1552 if (entry.view != null && entry.syncDataWithView) {
1553 String enteredText = ((TextView) entry.view.findViewById(R.id.data))
1554 .getText().toString();
1555 if (!TextUtils.isEmpty(enteredText)) {
1556 entry.data = enteredText;
1557 }
1558 }
1559
1560 // Build a new view
1561 final ViewGroup parent = mLayout;
1562 View view;
1563
1564 // Because we're emulating a ListView, we might need to handle focus changes
1565 // with some additional logic.
1566 if (entry.kind == Contacts.KIND_ORGANIZATION) {
1567 view = mInflater.inflate(R.layout.edit_contact_entry_org, parent, false);
1568 } else if (isOtherEntry(entry, People.CUSTOM_RINGTONE)) {
1569 view = mInflater.inflate(R.layout.edit_contact_entry_ringtone, parent, false);
1570 view.setOnFocusChangeListener(this);
1571 } else if (isOtherEntry(entry, People.SEND_TO_VOICEMAIL)) {
1572 view = mInflater.inflate(R.layout.edit_contact_entry_voicemail, parent, false);
1573 view.setOnFocusChangeListener(this);
1574 } else if (!entry.isStaticLabel) {
1575 view = mInflater.inflate(R.layout.edit_contact_entry, parent, false);
1576 } else {
1577 view = mInflater.inflate(R.layout.edit_contact_entry_static_label, parent, false);
1578 }
1579 entry.view = view;
1580
1581 // Set the entry as the tag so we can find it again later given just the view
1582 view.setTag(entry);
1583
1584 // Bind the label
1585 entry.bindLabel(this);
1586
1587 // Bind data
1588 TextView data = (TextView) view.findViewById(R.id.data);
1589 TextView data2 = (TextView) view.findViewById(R.id.data2);
1590
1591 if (data instanceof Button) {
1592 data.setOnClickListener(this);
1593 }
1594 if (data.length() == 0) {
1595 if (entry.syncDataWithView) {
1596 // If there is already data entered don't overwrite it
1597 data.setText(entry.data);
1598 } else {
1599 fillViewData(entry);
1600 }
1601 }
1602 if (data2 != null && data2.length() == 0) {
1603 // If there is already data entered don't overwrite it
1604 data2.setText(entry.data2);
1605 }
1606 data.setHint(entry.hint);
1607 if (data2 != null) data2.setHint(entry.hint2);
1608 if (entry.lines > 1) {
1609 data.setLines(entry.lines);
1610 data.setMaxLines(entry.maxLines);
1611 if (data2 != null) {
1612 data2.setLines(entry.lines);
1613 data2.setMaxLines(entry.maxLines);
1614 }
1615 }
1616 int contentType = entry.contentType;
1617 if (contentType != EditorInfo.TYPE_NULL) {
1618 data.setInputType(contentType);
1619 if (data2 != null) {
1620 data2.setInputType(contentType);
1621 }
1622 if ((contentType&EditorInfo.TYPE_MASK_CLASS)
1623 == EditorInfo.TYPE_CLASS_PHONE) {
1624 data.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
1625 if (data2 != null) {
1626 data2.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
1627 }
1628 }
1629 }
The Android Open Source Project928ccbd2009-03-05 14:34:37 -08001630
1631 // Give focus to children as requested, possibly after a configuration change
1632 View focusChild = view.findViewById(entry.requestFocusId);
1633 if (focusChild != null) {
1634 focusChild.requestFocus();
1635 if (focusChild instanceof EditText) {
1636 ((EditText) focusChild).setSelection(entry.requestCursor);
1637 }
1638 }
1639
1640 // Reset requested focus values
1641 entry.requestFocusId = View.NO_ID;
1642 entry.requestCursor = 0;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -08001643
1644 // Connect listeners up to watch for changed values.
1645 if (data instanceof EditText) {
1646 data.addTextChangedListener(this);
1647 }
1648 if (data2 instanceof EditText) {
1649 data2.addTextChangedListener(this);
1650 }
1651
1652 // Hook up the delete button
1653 View delete = view.findViewById(R.id.delete);
1654 if (delete != null) delete.setOnClickListener(this);
1655
1656 return view;
1657 }
1658
1659 private void fillViewData(final EditEntry entry) {
1660 if (isOtherEntry(entry, People.CUSTOM_RINGTONE)) {
1661 updateRingtoneView(entry);
1662 } else if (isOtherEntry(entry, People.SEND_TO_VOICEMAIL)) {
1663 CheckBox checkBox = (CheckBox) entry.view.findViewById(R.id.checkbox);
1664 boolean sendToVoicemail = false;
1665 if (entry.data != null) {
1666 sendToVoicemail = (Integer.valueOf(entry.data) == 1);
1667 }
1668 checkBox.setChecked(sendToVoicemail);
1669 }
1670 }
1671
1672 /**
1673 * Handles the results from the label change picker.
1674 */
1675 private final class LabelPickedListener implements DialogInterface.OnClickListener {
1676 EditEntry mEntry;
1677 String[] mLabels;
1678
1679 public LabelPickedListener(EditEntry entry, String[] labels) {
1680 mEntry = entry;
1681 mLabels = labels;
1682 }
1683
1684 public void onClick(DialogInterface dialog, int which) {
1685 // TODO: Use a managed dialog
1686 if (mEntry.kind != Contacts.KIND_IM) {
1687 final int type = getTypeFromLabelPosition(mLabels, which);
1688 if (type == ContactMethods.TYPE_CUSTOM) {
1689 createCustomPicker(mEntry, null);
1690 } else {
1691 mEntry.setLabel(EditContactActivity.this, type, mLabels[which]);
1692 mContactChanged = true;
1693 }
1694 } else {
1695 mEntry.setLabel(EditContactActivity.this, which, mLabels[which]);
1696 mContactChanged = true;
1697 }
1698 }
1699 }
1700
1701 /**
1702 * A basic structure with the data for a contact entry in the list.
1703 */
1704 private static final class EditEntry extends ContactEntryAdapter.Entry implements Parcelable {
1705 // These aren't stuffed into the parcel
1706 public EditContactActivity activity;
1707 public View view;
1708
1709 // These are stuffed into the parcel
1710 public String hint;
1711 public String hint2;
1712 public String column;
1713 public String contentDirectory;
1714 public String data2;
1715 public int contentType;
1716 public int type;
1717 /**
1718 * If 0 or 1, setSingleLine will be called. If negative, setSingleLine
1719 * will not be called.
1720 */
1721 public int lines = 1;
1722 public boolean isPrimary;
1723 public boolean isDeleted = false;
1724 public boolean isStaticLabel = false;
1725 public boolean syncDataWithView = true;
1726
The Android Open Source Project928ccbd2009-03-05 14:34:37 -08001727 /**
1728 * Request focus on the child of this {@link EditEntry} found using
1729 * {@link View#findViewById(int)}. This value should be reset to
1730 * {@link View#NO_ID} after each use.
1731 */
1732 public int requestFocusId = View.NO_ID;
1733
1734 /**
1735 * If the {@link #requestFocusId} is an {@link EditText}, this value
1736 * indicates the requested cursor position placement.
1737 */
1738 public int requestCursor = 0;
1739
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -08001740 private EditEntry() {
1741 // only used by CREATOR
1742 }
1743
1744 public EditEntry(EditContactActivity activity) {
1745 this.activity = activity;
1746 }
1747
1748 public EditEntry(EditContactActivity activity, String label,
1749 int type, String data, Uri uri, long id) {
1750 this.activity = activity;
1751 this.isPrimary = false;
1752 this.label = label;
1753 this.type = type;
1754 this.data = data;
1755 this.uri = uri;
1756 this.id = id;
1757 }
1758
1759 public int describeContents() {
1760 return 0;
1761 }
1762
1763 public void writeToParcel(Parcel parcel, int flags) {
1764 // Make sure to read data from the input field, if anything is entered
1765 data = getData();
1766
1767 // Write in our own fields.
1768 parcel.writeString(hint);
1769 parcel.writeString(hint2);
1770 parcel.writeString(column);
1771 parcel.writeString(contentDirectory);
1772 parcel.writeString(data2);
1773 parcel.writeInt(contentType);
1774 parcel.writeInt(type);
1775 parcel.writeInt(lines);
1776 parcel.writeInt(isPrimary ? 1 : 0);
1777 parcel.writeInt(isDeleted ? 1 : 0);
1778 parcel.writeInt(isStaticLabel ? 1 : 0);
1779 parcel.writeInt(syncDataWithView ? 1 : 0);
1780
1781 // Write in the fields from Entry
1782 super.writeToParcel(parcel);
1783 }
1784
1785 public static final Parcelable.Creator<EditEntry> CREATOR =
1786 new Parcelable.Creator<EditEntry>() {
1787 public EditEntry createFromParcel(Parcel in) {
1788 EditEntry entry = new EditEntry();
1789
1790 // Read out our own fields
1791 entry.hint = in.readString();
1792 entry.hint2 = in.readString();
1793 entry.column = in.readString();
1794 entry.contentDirectory = in.readString();
1795 entry.data2 = in.readString();
1796 entry.contentType = in.readInt();
1797 entry.type = in.readInt();
1798 entry.lines = in.readInt();
1799 entry.isPrimary = in.readInt() == 1;
1800 entry.isDeleted = in.readInt() == 1;
1801 entry.isStaticLabel = in.readInt() == 1;
1802 entry.syncDataWithView = in.readInt() == 1;
1803
1804 // Read out the fields from Entry
1805 entry.readFromParcel(in);
1806
1807 return entry;
1808 }
1809
1810 public EditEntry[] newArray(int size) {
1811 return new EditEntry[size];
1812 }
1813 };
1814
1815 public void setLabel(Context context, int typeIn, String labelIn) {
1816 type = typeIn;
1817 label = labelIn;
1818 if (view != null) {
1819 bindLabel(context);
1820 }
1821 }
1822
1823 public void bindLabel(Context context) {
1824 TextView v = (TextView) view.findViewById(R.id.label);
1825 if (isStaticLabel) {
1826 v.setText(label);
1827 return;
1828 }
1829
1830 switch (kind) {
1831 case Contacts.KIND_PHONE: {
1832 v.setText(Phones.getDisplayLabel(context, type, label));
1833 break;
1834 }
1835
1836 case Contacts.KIND_IM: {
1837 v.setText(getLabelsForKind(activity, kind)[type]);
1838 break;
1839 }
1840
1841 case Contacts.KIND_ORGANIZATION: {
1842 v.setText(Organizations.getDisplayLabel(activity, type, label));
1843 break;
1844 }
1845
1846 default: {
1847 v.setText(Contacts.ContactMethods.getDisplayLabel(context, kind, type, label));
1848 if (kind == Contacts.KIND_POSTAL) {
1849 v.setMaxLines(3);
1850 }
1851 break;
1852 }
1853 }
1854 v.setOnClickListener(activity);
1855 }
1856
1857 /**
1858 * Returns the data for the entry
1859 * @return the data for the entry
1860 */
1861 public String getData() {
1862 if (view != null && syncDataWithView) {
1863 CharSequence text = ((TextView) view.findViewById(R.id.data)).getText();
1864 if (text != null) {
1865 return text.toString();
1866 }
1867 }
1868
1869 if (data != null) {
1870 return data.toString();
1871 }
1872
1873 return null;
1874 }
1875
1876 /**
1877 * Dumps the entry into a HashMap suitable for passing to the database.
1878 *
1879 * @param values the HashMap to fill in.
1880 * @return true if the value should be saved, false otherwise
1881 */
1882 public boolean toValues(ContentValues values) {
1883 boolean success = false;
1884 String labelString = null;
1885 // Save the type and label
1886 if (view != null) {
1887 // Read the possibly updated label from the text field
1888 labelString = ((TextView) view.findViewById(R.id.label)).getText().toString();
1889 }
1890 switch (kind) {
1891 case Contacts.KIND_PHONE:
1892 if (type != Phones.TYPE_CUSTOM) {
1893 labelString = null;
1894 }
1895 values.put(Phones.LABEL, labelString);
1896 values.put(Phones.TYPE, type);
1897 break;
1898
1899 case Contacts.KIND_EMAIL:
1900 if (type != ContactMethods.TYPE_CUSTOM) {
1901 labelString = null;
1902 }
1903 values.put(ContactMethods.LABEL, labelString);
1904 values.put(ContactMethods.KIND, kind);
1905 values.put(ContactMethods.TYPE, type);
1906 break;
1907
1908 case Contacts.KIND_IM:
1909 values.put(ContactMethods.KIND, kind);
1910 values.put(ContactMethods.TYPE, ContactMethods.TYPE_OTHER);
1911 values.putNull(ContactMethods.LABEL);
1912 if (type != -1) {
1913 values.put(ContactMethods.AUX_DATA,
1914 ContactMethods.encodePredefinedImProtocol(type));
1915 } else {
1916 values.put(ContactMethods.AUX_DATA,
1917 ContactMethods.encodeCustomImProtocol(label.toString()));
1918 }
1919 break;
1920
1921 case Contacts.KIND_POSTAL:
1922 if (type != ContactMethods.TYPE_CUSTOM) {
1923 labelString = null;
1924 }
1925 values.put(ContactMethods.LABEL, labelString);
1926 values.put(ContactMethods.KIND, kind);
1927 values.put(ContactMethods.TYPE, type);
1928 break;
1929
1930 case Contacts.KIND_ORGANIZATION:
1931 if (type != ContactMethods.TYPE_CUSTOM) {
1932 labelString = null;
1933 }
1934 values.put(ContactMethods.LABEL, labelString);
1935 values.put(ContactMethods.TYPE, type);
1936 // Save the title
1937 if (view != null) {
1938 // Read the possibly updated data from the text field
1939 data2 = ((TextView) view.findViewById(R.id.data2)).getText().toString();
1940 }
1941 if (!TextUtils.isGraphic(data2)) {
1942 values.putNull(Organizations.TITLE);
1943 } else {
1944 values.put(Organizations.TITLE, data2.toString());
1945 success = true;
1946 }
1947 break;
1948
1949 default:
1950 Log.w(TAG, "unknown kind " + kind);
1951 values.put(ContactMethods.LABEL, labelString);
1952 values.put(ContactMethods.KIND, kind);
1953 values.put(ContactMethods.TYPE, type);
1954 break;
1955 }
1956
1957 // Only set the ISPRIMARY flag if part of the incoming data. This is because the
1958 // ContentProvider will try finding a new primary when setting to false, meaning
1959 // it's possible to lose primary altogether as we walk down the list. If this editor
1960 // implements editing of primaries in the future, this will need to be revisited.
1961 if (isPrimary) {
1962 values.put(ContactMethods.ISPRIMARY, 1);
1963 }
1964
1965 // Save the data
1966 if (view != null && syncDataWithView) {
1967 // Read the possibly updated data from the text field
1968 data = ((TextView) view.findViewById(R.id.data)).getText().toString();
1969 }
1970 if (!TextUtils.isGraphic(data)) {
1971 values.putNull(column);
1972 return success;
1973 } else {
1974 values.put(column, data.toString());
1975 return true;
1976 }
1977 }
1978
1979 /**
1980 * Create a new empty organization entry
1981 */
1982 public static final EditEntry newOrganizationEntry(EditContactActivity activity,
1983 Uri uri, int type) {
1984 return newOrganizationEntry(activity, null, type, null, null, uri, 0);
1985 }
1986
1987 /**
1988 * Create a new company entry with the given data.
1989 */
1990 public static final EditEntry newOrganizationEntry(EditContactActivity activity,
1991 String label, int type, String company, String title, Uri uri, long id) {
1992 EditEntry entry = new EditEntry(activity, label, type, company, uri, id);
1993 entry.hint = activity.getString(R.string.ghostData_company);
1994 entry.hint2 = activity.getString(R.string.ghostData_title);
1995 entry.data2 = title;
1996 entry.column = Organizations.COMPANY;
1997 entry.contentDirectory = Organizations.CONTENT_DIRECTORY;
1998 entry.kind = Contacts.KIND_ORGANIZATION;
1999 entry.contentType = EditorInfo.TYPE_CLASS_TEXT
2000 | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
2001 return entry;
2002 }
2003
2004 /**
2005 * Create a new notes entry with the given data.
2006 */
2007 public static final EditEntry newNotesEntry(EditContactActivity activity,
2008 String data, Uri uri) {
2009 EditEntry entry = new EditEntry(activity);
2010 entry.label = activity.getString(R.string.label_notes);
2011 entry.hint = activity.getString(R.string.ghostData_notes);
2012 entry.data = data;
2013 entry.uri = uri;
2014 entry.column = People.NOTES;
2015 entry.maxLines = 10;
2016 entry.lines = 2;
2017 entry.id = 0;
2018 entry.kind = KIND_CONTACT;
2019 entry.contentType = EditorInfo.TYPE_CLASS_TEXT
2020 | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
2021 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
2022 entry.isStaticLabel = true;
2023 return entry;
2024 }
2025
2026 /**
2027 * Create a new ringtone entry with the given data.
2028 */
2029 public static final EditEntry newRingtoneEntry(EditContactActivity activity,
2030 String data, Uri uri) {
2031 EditEntry entry = new EditEntry(activity);
2032 entry.label = activity.getString(R.string.label_ringtone);
2033 entry.data = data;
2034 entry.uri = uri;
2035 entry.column = People.CUSTOM_RINGTONE;
2036 entry.kind = KIND_CONTACT;
2037 entry.isStaticLabel = true;
2038 entry.syncDataWithView = false;
2039 entry.lines = -1;
2040 return entry;
2041 }
2042
2043 /**
2044 * Create a new send-to-voicemail entry with the given data.
2045 */
2046 public static final EditEntry newSendToVoicemailEntry(EditContactActivity activity,
2047 String data, Uri uri) {
2048 EditEntry entry = new EditEntry(activity);
2049 entry.label = activity.getString(R.string.actionIncomingCall);
2050 entry.data = data;
2051 entry.uri = uri;
2052 entry.column = People.SEND_TO_VOICEMAIL;
2053 entry.kind = KIND_CONTACT;
2054 entry.isStaticLabel = true;
2055 entry.syncDataWithView = false;
2056 entry.lines = -1;
2057 return entry;
2058 }
2059
2060 /**
2061 * Create a new empty email entry
2062 */
2063 public static final EditEntry newPhoneEntry(EditContactActivity activity,
2064 Uri uri, int type) {
2065 return newPhoneEntry(activity, null, type, null, uri, 0);
2066 }
2067
2068 /**
2069 * Create a new phone entry with the given data.
2070 */
2071 public static final EditEntry newPhoneEntry(EditContactActivity activity,
2072 String label, int type, String data, Uri uri,
2073 long id) {
2074 EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
2075 entry.hint = activity.getString(R.string.ghostData_phone);
2076 entry.column = People.Phones.NUMBER;
2077 entry.contentDirectory = People.Phones.CONTENT_DIRECTORY;
2078 entry.kind = Contacts.KIND_PHONE;
2079 entry.contentType = EditorInfo.TYPE_CLASS_PHONE;
2080 return entry;
2081 }
2082
2083 /**
2084 * Create a new empty email entry
2085 */
2086 public static final EditEntry newEmailEntry(EditContactActivity activity,
2087 Uri uri, int type) {
2088 return newEmailEntry(activity, null, type, null, uri, 0);
2089 }
2090
2091 /**
2092 * Create a new email entry with the given data.
2093 */
2094 public static final EditEntry newEmailEntry(EditContactActivity activity,
2095 String label, int type, String data, Uri uri,
2096 long id) {
2097 EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
2098 entry.hint = activity.getString(R.string.ghostData_email);
2099 entry.column = ContactMethods.DATA;
2100 entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
2101 entry.kind = Contacts.KIND_EMAIL;
2102 entry.contentType = EditorInfo.TYPE_CLASS_TEXT
2103 | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
2104 return entry;
2105 }
2106
2107 /**
2108 * Create a new empty postal address entry
2109 */
2110 public static final EditEntry newPostalEntry(EditContactActivity activity,
2111 Uri uri, int type) {
2112 return newPostalEntry(activity, null, type, null, uri, 0);
2113 }
2114
2115 /**
2116 * Create a new postal address entry with the given data.
2117 *
2118 * @param label label for the item, from the db not the display label
2119 * @param type the type of postal address
2120 * @param data the starting data for the entry, may be null
2121 * @param uri the uri for the entry if it already exists, may be null
2122 * @param id the id for the entry if it already exists, 0 it it doesn't
2123 * @return the new EditEntry
2124 */
2125 public static final EditEntry newPostalEntry(EditContactActivity activity,
2126 String label, int type, String data, Uri uri, long id) {
2127 EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
2128 entry.hint = activity.getString(R.string.ghostData_postal);
2129 entry.column = ContactMethods.DATA;
2130 entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
2131 entry.kind = Contacts.KIND_POSTAL;
2132 entry.contentType = EditorInfo.TYPE_CLASS_TEXT
2133 | EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS
2134 | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS
2135 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
2136 entry.maxLines = 4;
2137 entry.lines = 2;
2138 return entry;
2139 }
2140
2141 /**
2142 * Create a new IM address entry
2143 */
2144 public static final EditEntry newImEntry(EditContactActivity activity,
2145 Uri uri, int type) {
2146 return newImEntry(activity, null, type, null, uri, 0);
2147 }
2148
2149 /**
2150 * Create a new IM address entry with the given data.
2151 *
2152 * @param label label for the item, from the db not the display label
2153 * @param protocol the type used
2154 * @param data the starting data for the entry, may be null
2155 * @param uri the uri for the entry if it already exists, may be null
2156 * @param id the id for the entry if it already exists, 0 it it doesn't
2157 * @return the new EditEntry
2158 */
2159 public static final EditEntry newImEntry(EditContactActivity activity,
2160 String label, int protocol, String data, Uri uri, long id) {
2161 EditEntry entry = new EditEntry(activity, label, protocol, data, uri, id);
2162 entry.hint = activity.getString(R.string.ghostData_im);
2163 entry.column = ContactMethods.DATA;
2164 entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
2165 entry.kind = Contacts.KIND_IM;
The Android Open Source Project928ccbd2009-03-05 14:34:37 -08002166 entry.contentType = EditorInfo.TYPE_CLASS_TEXT
2167 | EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -08002168 return entry;
2169 }
2170 }
2171
2172 public void afterTextChanged(Editable s) {
2173 // Someone edited a text field, so assume this contact is changed
2174 mContactChanged = true;
2175 }
2176
2177 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
2178 // Do nothing; editing handled by afterTextChanged()
2179 }
2180
2181 public void onTextChanged(CharSequence s, int start, int before, int count) {
2182 // Do nothing; editing handled by afterTextChanged()
2183 }
2184
2185 public void onFocusChange(View v, boolean hasFocus) {
2186 // Because we're emulating a ListView, we need to setSelected() for
2187 // views as they are focused.
2188 v.setSelected(hasFocus);
2189 }
2190}