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