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