blob: b89573b70fd594f5796083c6a0e4312caca3a58d [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.contacts;
import static com.android.contacts.ContactEntryAdapter.CONTACT_CUSTOM_RINGTONE_COLUMN;
import static com.android.contacts.ContactEntryAdapter.CONTACT_NAME_COLUMN;
import static com.android.contacts.ContactEntryAdapter.CONTACT_NOTES_COLUMN;
import static com.android.contacts.ContactEntryAdapter.CONTACT_PROJECTION;
import static com.android.contacts.ContactEntryAdapter.CONTACT_SEND_TO_VOICEMAIL_COLUMN;
import static com.android.contacts.ContactEntryAdapter.CONTACT_PHONETIC_NAME_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_AUX_DATA_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_DATA_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_ID_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_ISPRIMARY_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_KIND_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_LABEL_COLUMN;
import static com.android.contacts.ContactEntryAdapter.METHODS_PROJECTION;
import static com.android.contacts.ContactEntryAdapter.METHODS_TYPE_COLUMN;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_COMPANY_COLUMN;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_ID_COLUMN;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_ISPRIMARY_COLUMN;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_LABEL_COLUMN;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_PROJECTION;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_TITLE_COLUMN;
import static com.android.contacts.ContactEntryAdapter.ORGANIZATIONS_TYPE_COLUMN;
import static com.android.contacts.ContactEntryAdapter.PHONES_ID_COLUMN;
import static com.android.contacts.ContactEntryAdapter.PHONES_ISPRIMARY_COLUMN;
import static com.android.contacts.ContactEntryAdapter.PHONES_LABEL_COLUMN;
import static com.android.contacts.ContactEntryAdapter.PHONES_NUMBER_COLUMN;
import static com.android.contacts.ContactEntryAdapter.PHONES_PROJECTION;
import static com.android.contacts.ContactEntryAdapter.PHONES_TYPE_COLUMN;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.provider.Contacts;
import android.provider.Contacts.ContactMethods;
import android.provider.Contacts.Intents.Insert;
import android.provider.Contacts.Groups;
import android.provider.Contacts.Organizations;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
import android.telephony.PhoneNumberFormattingTextWatcher;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.TextKeyListener;
import android.text.method.TextKeyListener.Capitalize;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
/**
* Activity for editing or inserting a contact. Note that if the contact data changes in the
* background while this activity is running, the updates will be overwritten.
*/
public final class EditContactActivity extends Activity implements View.OnClickListener,
TextWatcher, View.OnFocusChangeListener {
private static final String TAG = "EditContactActivity";
private static final int STATE_UNKNOWN = 0;
/** Editing an existing contact */
private static final int STATE_EDIT = 1;
/** The full insert mode */
private static final int STATE_INSERT = 2;
/** The launch code when picking a photo and the raw data is returned */
private static final int PHOTO_PICKED_WITH_DATA = 3021;
/** The launch code when picking a ringtone */
private static final int RINGTONE_PICKED = 3023;
// These correspond to the string array in resources for picker "other" items
final static int OTHER_ORGANIZATION = 0;
final static int OTHER_NOTE = 1;
// Dialog IDs
final static int DELETE_CONFIRMATION_DIALOG = 2;
// Section IDs
final static int SECTION_PHONES = 3;
final static int SECTION_EMAIL = 4;
final static int SECTION_IM = 5;
final static int SECTION_POSTAL = 6;
final static int SECTION_ORG = 7;
final static int SECTION_NOTE = 8;
// Menu item IDs
public static final int MENU_ITEM_SAVE = 1;
public static final int MENU_ITEM_DONT_SAVE = 2;
public static final int MENU_ITEM_DELETE = 3;
public static final int MENU_ITEM_PHOTO = 6;
/** Used to represent an invalid type for a contact entry */
private static final int INVALID_TYPE = -1;
/** The default type for a phone that is added via an intent */
private static final int DEFAULT_PHONE_TYPE = Phones.TYPE_MOBILE;
/** The default type for an email that is added via an intent */
private static final int DEFAULT_EMAIL_TYPE = ContactMethods.TYPE_HOME;
/** The default type for a postal address that is added via an intent */
private static final int DEFAULT_POSTAL_TYPE = ContactMethods.TYPE_HOME;
private int mState; // saved across instances
private boolean mInsert; // saved across instances
private Uri mUri; // saved across instances
/** In insert mode this is the photo */
private Bitmap mPhoto; // saved across instances
private boolean mPhotoChanged = false; // saved across instances
private EditText mNameView;
private ImageView mPhotoImageView;
private ViewGroup mContentView;
private LinearLayout mLayout;
private LayoutInflater mInflater;
private MenuItem mPhotoMenuItem;
private boolean mPhotoPresent = false;
private EditText mPhoneticNameView; // invisible in some locales, but always present
/** Flag marking this contact as changed, meaning we should write changes back. */
private boolean mContactChanged = false;
// These are accessed by inner classes. They're package scoped to make access more efficient.
/* package */ ContentResolver mResolver;
/* package */ ArrayList<EditEntry> mPhoneEntries = new ArrayList<EditEntry>();
/* package */ ArrayList<EditEntry> mEmailEntries = new ArrayList<EditEntry>();
/* package */ ArrayList<EditEntry> mImEntries = new ArrayList<EditEntry>();
/* package */ ArrayList<EditEntry> mPostalEntries = new ArrayList<EditEntry>();
/* package */ ArrayList<EditEntry> mOrgEntries = new ArrayList<EditEntry>();
/* package */ ArrayList<EditEntry> mNoteEntries = new ArrayList<EditEntry>();
/* package */ ArrayList<EditEntry> mOtherEntries = new ArrayList<EditEntry>();
/* package */ ArrayList<ArrayList<EditEntry>> mSections = new ArrayList<ArrayList<EditEntry>>();
/* package */ static final int MSG_DELETE = 1;
/* package */ static final int MSG_CHANGE_LABEL = 2;
/* package */ static final int MSG_ADD_PHONE = 3;
/* package */ static final int MSG_ADD_EMAIL = 4;
/* package */ static final int MSG_ADD_POSTAL = 5;
private static final int[] TYPE_PRECEDENCE_PHONES = new int[] {
Phones.TYPE_MOBILE, Phones.TYPE_HOME, Phones.TYPE_WORK, Phones.TYPE_OTHER
};
private static final int[] TYPE_PRECEDENCE_METHODS = new int[] {
ContactMethods.TYPE_HOME, ContactMethods.TYPE_WORK, ContactMethods.TYPE_OTHER
};
private static final int[] TYPE_PRECEDENCE_IM = new int[] {
ContactMethods.PROTOCOL_GOOGLE_TALK, ContactMethods.PROTOCOL_AIM,
ContactMethods.PROTOCOL_MSN, ContactMethods.PROTOCOL_YAHOO,
ContactMethods.PROTOCOL_JABBER
};
private static final int[] TYPE_PRECEDENCE_ORG = new int[] {
Organizations.TYPE_WORK, Organizations.TYPE_OTHER
};
public void onClick(View v) {
switch (v.getId()) {
case R.id.photoImage: {
doPickPhotoAction();
break;
}
case R.id.checkable: {
CheckBox checkBox = (CheckBox) v.findViewById(R.id.checkbox);
checkBox.toggle();
EditEntry entry = findEntryForView(v);
entry.data = checkBox.isChecked() ? "1" : "0";
mContactChanged = true;
break;
}
case R.id.entry_ringtone: {
EditEntry entry = findEntryForView(v);
doPickRingtone(entry);
break;
}
case R.id.separator: {
// Someone clicked on a section header, so handle add action
int sectionType = (Integer) v.getTag();
doAddAction(sectionType);
break;
}
case R.id.saveButton:
doSaveAction();
break;
case R.id.discardButton:
doRevertAction();
break;
case R.id.delete: {
EditEntry entry = findEntryForView(v);
if (entry != null) {
// Clear the text and hide the view so it gets saved properly
((TextView) entry.view.findViewById(R.id.data)).setText(null);
entry.view.setVisibility(View.GONE);
entry.isDeleted = true;
}
// Force rebuild of views because section headers might need to change
buildViews();
break;
}
case R.id.label: {
EditEntry entry = findEntryForView(v);
if (entry != null) {
String[] labels = getLabelsForKind(this, entry.kind);
LabelPickedListener listener = new LabelPickedListener(entry, labels);
new AlertDialog.Builder(EditContactActivity.this)
.setItems(labels, listener)
.setTitle(R.string.selectLabel)
.show();
}
break;
}
}
}
private void setPhotoPresent(boolean present) {
mPhotoPresent = present;
// Correctly scale the contact photo if present, otherwise just center
// the photo placeholder icon.
if (mPhotoPresent) {
mPhotoImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
} else {
mPhotoImageView.setImageResource(R.drawable.ic_menu_add_picture);
mPhotoImageView.setScaleType(ImageView.ScaleType.CENTER);
}
if (mPhotoMenuItem != null) {
if (present) {
mPhotoMenuItem.setTitle(R.string.removePicture);
mPhotoMenuItem.setIcon(android.R.drawable.ic_menu_delete);
} else {
mPhotoMenuItem.setTitle(R.string.addPicture);
mPhotoMenuItem.setIcon(R.drawable.ic_menu_add_picture);
}
}
}
private EditEntry findEntryForView(View v) {
// Try to find the entry for this view
EditEntry entry = null;
do {
Object tag = v.getTag();
if (tag != null && tag instanceof EditEntry) {
entry = (EditEntry) tag;
break;
} else {
ViewParent parent = v.getParent();
if (parent != null && parent instanceof View) {
v = (View) parent;
} else {
v = null;
}
}
} while (v != null);
return entry;
}
private DialogInterface.OnClickListener mDeleteContactDialogListener =
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int button) {
mResolver.delete(mUri, null, null);
finish();
}
};
private boolean mMobilePhoneAdded = false;
private boolean mPrimaryEmailAdded = false;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
mResolver = getContentResolver();
// Build the list of sections
setupSections();
// Load the UI
mInflater = getLayoutInflater();
mContentView = (ViewGroup)mInflater.inflate(R.layout.edit_contact, null);
setContentView(mContentView);
mLayout = (LinearLayout) findViewById(R.id.list);
mNameView = (EditText) findViewById(R.id.name);
mPhotoImageView = (ImageView) findViewById(R.id.photoImage);
mPhotoImageView.setOnClickListener(this);
mPhoneticNameView = (EditText) findViewById(R.id.phonetic_name);
// Setup the bottom buttons
View view = findViewById(R.id.saveButton);
view.setOnClickListener(this);
view = findViewById(R.id.discardButton);
view.setOnClickListener(this);
// Resolve the intent
mState = STATE_UNKNOWN;
Intent intent = getIntent();
String action = intent.getAction();
mUri = intent.getData();
if (mUri != null) {
if (action.equals(Intent.ACTION_EDIT)) {
if (icicle == null) {
// Build the entries & views
buildEntriesForEdit(getIntent().getExtras());
buildViews();
}
setTitle(R.string.editContact_title_edit);
mState = STATE_EDIT;
} else if (action.equals(Intent.ACTION_INSERT)) {
if (icicle == null) {
// Build the entries & views
buildEntriesForInsert(getIntent().getExtras());
buildViews();
}
setTitle(R.string.editContact_title_insert);
mState = STATE_INSERT;
mInsert = true;
}
}
if (mState == STATE_UNKNOWN) {
Log.e(TAG, "Cannot resolve intent: " + intent);
finish();
return;
}
if (mState == STATE_EDIT) {
setTitle(getResources().getText(R.string.editContact_title_edit));
} else {
setTitle(getResources().getText(R.string.editContact_title_insert));
}
}
private void setupSections() {
mSections.add(mPhoneEntries);
mSections.add(mEmailEntries);
mSections.add(mImEntries);
mSections.add(mPostalEntries);
mSections.add(mOrgEntries);
mSections.add(mNoteEntries);
mSections.add(mOtherEntries);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// To store current focus between config changes, follow focus down the
// view tree, keeping track of any parents with EditEntry tags
View focusedChild = mContentView.getFocusedChild();
EditEntry focusedEntry = null;
while (focusedChild != null) {
Object tag = focusedChild.getTag();
if (tag instanceof EditEntry) {
focusedEntry = (EditEntry) tag;
}
// Keep going deeper until child isn't a group
if (focusedChild instanceof ViewGroup) {
View deeperFocus = ((ViewGroup) focusedChild).getFocusedChild();
if (deeperFocus != null) {
focusedChild = deeperFocus;
} else {
break;
}
} else {
break;
}
}
if (focusedChild != null) {
int requestFocusId = focusedChild.getId();
int requestCursor = 0;
if (focusedChild instanceof EditText) {
requestCursor = ((EditText) focusedChild).getSelectionStart();
}
// Store focus values in EditEntry if found, otherwise store as
// generic values
if (focusedEntry != null) {
focusedEntry.requestFocusId = requestFocusId;
focusedEntry.requestCursor = requestCursor;
} else {
outState.putInt("requestFocusId", requestFocusId);
outState.putInt("requestCursor", requestCursor);
}
}
outState.putParcelableArrayList("phoneEntries", mPhoneEntries);
outState.putParcelableArrayList("emailEntries", mEmailEntries);
outState.putParcelableArrayList("imEntries", mImEntries);
outState.putParcelableArrayList("postalEntries", mPostalEntries);
outState.putParcelableArrayList("orgEntries", mOrgEntries);
outState.putParcelableArrayList("noteEntries", mNoteEntries);
outState.putParcelableArrayList("otherEntries", mOtherEntries);
outState.putInt("state", mState);
outState.putBoolean("insert", mInsert);
outState.putParcelable("uri", mUri);
outState.putString("name", mNameView.getText().toString());
outState.putParcelable("photo", mPhoto);
outState.putBoolean("photoChanged", mPhotoChanged);
outState.putString("phoneticName", mPhoneticNameView.getText().toString());
outState.putBoolean("contactChanged", mContactChanged);
}
@Override
protected void onRestoreInstanceState(Bundle inState) {
mPhoneEntries = inState.getParcelableArrayList("phoneEntries");
mEmailEntries = inState.getParcelableArrayList("emailEntries");
mImEntries = inState.getParcelableArrayList("imEntries");
mPostalEntries = inState.getParcelableArrayList("postalEntries");
mOrgEntries = inState.getParcelableArrayList("orgEntries");
mNoteEntries = inState.getParcelableArrayList("noteEntries");
mOtherEntries = inState.getParcelableArrayList("otherEntries");
setupSections();
mState = inState.getInt("state");
mInsert = inState.getBoolean("insert");
mUri = inState.getParcelable("uri");
mNameView.setText(inState.getString("name"));
mPhoto = inState.getParcelable("photo");
if (mPhoto != null) {
mPhotoImageView.setImageBitmap(mPhoto);
setPhotoPresent(true);
} else {
mPhotoImageView.setImageResource(R.drawable.ic_contact_picture);
setPhotoPresent(false);
}
mPhotoChanged = inState.getBoolean("photoChanged");
mPhoneticNameView.setText(inState.getString("phoneticName"));
mContactChanged = inState.getBoolean("contactChanged");
// Now that everything is restored, build the view
buildViews();
// Try restoring any generally requested focus
int requestFocusId = inState.getInt("requestFocusId", View.NO_ID);
View focusedChild = mContentView.findViewById(requestFocusId);
if (focusedChild != null) {
focusedChild.requestFocus();
if (focusedChild instanceof EditText) {
int requestCursor = inState.getInt("requestCursor", 0);
((EditText) focusedChild).setSelection(requestCursor);
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != RESULT_OK) {
return;
}
switch (requestCode) {
case PHOTO_PICKED_WITH_DATA: {
final Bundle extras = data.getExtras();
if (extras != null) {
Bitmap photo = extras.getParcelable("data");
mPhoto = photo;
mPhotoChanged = true;
mPhotoImageView.setImageBitmap(photo);
setPhotoPresent(true);
}
break;
}
case RINGTONE_PICKED: {
Uri pickedUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
handleRingtonePicked(pickedUri);
mContactChanged = true;
break;
}
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK: {
doSaveAction();
return true;
}
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, MENU_ITEM_SAVE, 0, R.string.menu_done)
.setIcon(android.R.drawable.ic_menu_save)
.setAlphabeticShortcut('\n');
menu.add(0, MENU_ITEM_DONT_SAVE, 0, R.string.menu_doNotSave)
.setIcon(android.R.drawable.ic_menu_close_clear_cancel)
.setAlphabeticShortcut('q');
if (!mInsert) {
menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact)
.setIcon(android.R.drawable.ic_menu_delete);
}
mPhotoMenuItem = menu.add(0, MENU_ITEM_PHOTO, 0, null);
// Updates the state of the menu item
setPhotoPresent(mPhotoPresent);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_ITEM_SAVE:
doSaveAction();
return true;
case MENU_ITEM_DONT_SAVE:
doRevertAction();
return true;
case MENU_ITEM_DELETE:
// Get confirmation
showDialog(DELETE_CONFIRMATION_DIALOG);
return true;
case MENU_ITEM_PHOTO:
if (!mPhotoPresent) {
doPickPhotoAction();
} else {
doRemovePhotoAction();
}
return true;
}
return false;
}
/**
* Try guessing the next-best type of {@link EditEntry} to insert into the
* given list. We walk down the precedence list until we find a type that
* doesn't exist yet, or default to the lowest ranking type.
*/
private int guessNextType(ArrayList<EditEntry> entries, int[] precedenceList) {
// Keep track of the types we've seen already
SparseBooleanArray existAlready = new SparseBooleanArray(entries.size());
for (int i = entries.size() - 1; i >= 0; i--) {
EditEntry entry = entries.get(i);
if (!entry.isDeleted) {
existAlready.put(entry.type, true);
}
}
// Pick the first item we haven't seen
for (int type : precedenceList) {
if (!existAlready.get(type, false)) {
return type;
}
}
// Otherwise default to last item
return precedenceList[precedenceList.length - 1];
}
private void doAddAction(int sectionType) {
EditEntry entry = null;
switch (sectionType) {
case SECTION_PHONES: {
// Try figuring out which type to insert next
int nextType = guessNextType(mPhoneEntries, TYPE_PRECEDENCE_PHONES);
entry = EditEntry.newPhoneEntry(EditContactActivity.this,
Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY),
nextType);
mPhoneEntries.add(entry);
break;
}
case SECTION_EMAIL: {
// Try figuring out which type to insert next
int nextType = guessNextType(mEmailEntries, TYPE_PRECEDENCE_METHODS);
entry = EditEntry.newEmailEntry(EditContactActivity.this,
Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
nextType);
mEmailEntries.add(entry);
break;
}
case SECTION_IM: {
// Try figuring out which type to insert next
int nextType = guessNextType(mImEntries, TYPE_PRECEDENCE_IM);
entry = EditEntry.newImEntry(EditContactActivity.this,
Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
nextType);
mImEntries.add(entry);
break;
}
case SECTION_POSTAL: {
int nextType = guessNextType(mPostalEntries, TYPE_PRECEDENCE_METHODS);
entry = EditEntry.newPostalEntry(EditContactActivity.this,
Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
nextType);
mPostalEntries.add(entry);
break;
}
case SECTION_ORG: {
int nextType = guessNextType(mOrgEntries, TYPE_PRECEDENCE_ORG);
entry = EditEntry.newOrganizationEntry(EditContactActivity.this,
Uri.withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY),
nextType);
mOrgEntries.add(entry);
break;
}
case SECTION_NOTE: {
entry = EditEntry.newNotesEntry(EditContactActivity.this, null, mUri);
mNoteEntries.add(entry);
break;
}
}
// Rebuild the views if needed
if (entry != null) {
buildViews();
mContactChanged = true;
View dataView = entry.view.findViewById(R.id.data);
if (dataView == null) {
entry.view.requestFocus();
} else {
dataView.requestFocus();
}
}
}
private void doRevertAction() {
finish();
}
private void doPickPhotoAction() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
// TODO: get these values from constants somewhere
intent.setType("image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 96);
intent.putExtra("outputY", 96);
try {
intent.putExtra("return-data", true);
startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
} catch (ActivityNotFoundException e) {
new AlertDialog.Builder(EditContactActivity.this)
.setTitle(R.string.errorDialogTitle)
.setMessage(R.string.photoPickerNotFoundText)
.setPositiveButton(android.R.string.ok, null)
.show();
}
}
private void doRemovePhotoAction() {
mPhoto = null;
mPhotoChanged = true;
setPhotoPresent(false);
}
private void doPickRingtone(EditEntry entry) {
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
// Allow user to pick 'Default'
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
// Show only ringtones
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE);
// Don't show 'Silent'
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
Uri ringtoneUri;
if (entry.data != null) {
ringtoneUri = Uri.parse(entry.data);
} else {
// Otherwise pick default ringtone Uri so that something is selected.
ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
}
// Put checkmark next to the current ringtone for this contact
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
// Launch!
startActivityForResult(intent, RINGTONE_PICKED);
}
private void handleRingtonePicked(Uri pickedUri) {
EditEntry entry = getOtherEntry(People.CUSTOM_RINGTONE);
if (entry == null) {
Log.w(TAG, "Ringtone picked but could not find ringtone entry");
return;
}
if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) {
entry.data = null;
} else {
entry.data = pickedUri.toString();
}
updateRingtoneView(entry);
}
private void updateRingtoneView(EditEntry entry) {
String ringtoneName;
if (entry.data == null) {
ringtoneName = getString(R.string.default_ringtone);
} else {
Uri ringtoneUri = Uri.parse(entry.data);
Ringtone ringtone = RingtoneManager.getRingtone(this, ringtoneUri);
if (ringtone == null) {
Log.w(TAG, "ringtone's URI doesn't resolve to a Ringtone");
return;
}
ringtoneName = ringtone.getTitle(this);
}
updateDataView(entry, ringtoneName);
}
private void updateDataView(EditEntry entry, String text) {
TextView dataView = (TextView) entry.view.findViewById(R.id.data);
dataView.setText(text);
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DELETE_CONFIRMATION_DIALOG:
return new AlertDialog.Builder(EditContactActivity.this)
.setTitle(R.string.deleteConfirmation_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.deleteConfirmation)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, mDeleteContactDialogListener)
.setCancelable(false)
.create();
}
return super.onCreateDialog(id);
}
static String[] getLabelsForKind(Context context, int kind) {
final Resources resources = context.getResources();
switch (kind) {
case Contacts.KIND_PHONE:
return resources.getStringArray(android.R.array.phoneTypes);
case Contacts.KIND_EMAIL:
return resources.getStringArray(android.R.array.emailAddressTypes);
case Contacts.KIND_POSTAL:
return resources.getStringArray(android.R.array.postalAddressTypes);
case Contacts.KIND_IM:
return resources.getStringArray(android.R.array.imProtocols);
case Contacts.KIND_ORGANIZATION:
return resources.getStringArray(android.R.array.organizationTypes);
case EditEntry.KIND_CONTACT:
return resources.getStringArray(R.array.otherLabels);
}
return null;
}
int getTypeFromLabelPosition(CharSequence[] labels, int labelPosition) {
// In the UI Custom... comes last, but it is uses the constant 0
// so it is in the same location across the various kinds. Fix up the
// position to a valid type here.
if (labelPosition == labels.length - 1) {
return ContactMethods.TYPE_CUSTOM;
} else {
return labelPosition + 1;
}
}
private EditEntry getOtherEntry(String column) {
for (int i = mOtherEntries.size() - 1; i >= 0; i--) {
EditEntry entry = mOtherEntries.get(i);
if (isOtherEntry(entry, column)) {
return entry;
}
}
return null;
}
private static boolean isOtherEntry(EditEntry entry, String column) {
return entry != null && entry.column != null && entry.column.equals(column);
}
private void createCustomPicker(final EditEntry entry, final ArrayList<EditEntry> addTo) {
final EditText label = new EditText(this);
label.setKeyListener(TextKeyListener.getInstance(false, Capitalize.WORDS));
label.requestFocus();
new AlertDialog.Builder(this)
.setView(label)
.setTitle(R.string.customLabelPickerTitle)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
entry.setLabel(EditContactActivity.this, ContactMethods.TYPE_CUSTOM,
label.getText().toString());
mContactChanged = true;
if (addTo != null) {
addTo.add(entry);
buildViews();
entry.view.requestFocus(View.FOCUS_DOWN);
}
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
/**
* Saves or creates the contact based on the mode, and if sucessful finishes the activity.
*/
private void doSaveAction() {
// Save or create the contact if needed
switch (mState) {
case STATE_EDIT:
save();
break;
case STATE_INSERT:
create();
break;
default:
Log.e(TAG, "Unknown state in doSaveOrCreate: " + mState);
break;
}
finish();
}
/**
* Save the various fields to the existing contact.
*/
private void save() {
ContentValues values = new ContentValues();
String data;
int numValues = 0;
// Handle the name and send to voicemail specially
final String name = mNameView.getText().toString();
if (name != null && TextUtils.isGraphic(name)) {
numValues++;
}
values.put(People.NAME, name);
values.put(People.PHONETIC_NAME, mPhoneticNameView.getText().toString());
mResolver.update(mUri, values, null, null);
if (mPhotoChanged) {
// Only write the photo if it's changed, since we don't initially load mPhoto
if (mPhoto != null) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
mPhoto.compress(Bitmap.CompressFormat.JPEG, 75, stream);
Contacts.People.setPhotoData(mResolver, mUri, stream.toByteArray());
} else {
Contacts.People.setPhotoData(mResolver, mUri, null);
}
}
int entryCount = ContactEntryAdapter.countEntries(mSections, false);
for (int i = 0; i < entryCount; i++) {
EditEntry entry = ContactEntryAdapter.getEntry(mSections, i, false);
int kind = entry.kind;
data = entry.getData();
boolean empty = data == null || !TextUtils.isGraphic(data);
if (kind == EditEntry.KIND_CONTACT) {
values.clear();
if (!empty) {
values.put(entry.column, data);
mResolver.update(entry.uri, values, null, null);
if (!People.CUSTOM_RINGTONE.equals(entry.column) &&
!People.SEND_TO_VOICEMAIL.equals(entry.column)) {
numValues++;
}
} else {
values.put(entry.column, (String) null);
mResolver.update(entry.uri, values, null, null);
}
} else {
if (!empty) {
values.clear();
entry.toValues(values);
if (entry.id != 0) {
mResolver.update(entry.uri, values, null, null);
} else {
mResolver.insert(entry.uri, values);
}
if (!People.CUSTOM_RINGTONE.equals(entry.column) &&
!People.SEND_TO_VOICEMAIL.equals(entry.column)) {
numValues++;
}
} else if (entry.id != 0) {
mResolver.delete(entry.uri, null, null);
}
}
}
if (numValues == 0) {
// The contact is completely empty, delete it
mResolver.delete(mUri, null, null);
mUri = null;
setResult(RESULT_CANCELED);
} else {
// Add the entry to the my contacts group if it isn't there already
People.addToMyContactsGroup(mResolver, ContentUris.parseId(mUri));
setResult(RESULT_OK, new Intent().setData(mUri));
// Only notify user if we actually changed contact
if (mContactChanged || mPhotoChanged) {
Toast.makeText(this, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
}
}
}
/**
* Takes the entered data and saves it to a new contact.
*/
private void create() {
ContentValues values = new ContentValues();
String data;
int numValues = 0;
// Create the contact itself
final String name = mNameView.getText().toString();
if (name != null && TextUtils.isGraphic(name)) {
numValues++;
}
values.put(People.NAME, name);
values.put(People.PHONETIC_NAME, mPhoneticNameView.getText().toString());
// Add the contact to the My Contacts group
Uri contactUri = People.createPersonInMyContactsGroup(mResolver, values);
// Add the contact to the group that is being displayed in the contact list
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
int displayType = prefs.getInt(ContactsListActivity.PREF_DISPLAY_TYPE,
ContactsListActivity.DISPLAY_TYPE_UNKNOWN);
if (displayType == ContactsListActivity.DISPLAY_TYPE_USER_GROUP) {
String displayGroup = prefs.getString(ContactsListActivity.PREF_DISPLAY_INFO,
null);
if (!TextUtils.isEmpty(displayGroup)) {
People.addToGroup(mResolver, ContentUris.parseId(contactUri), displayGroup);
}
} else {
// Check to see if we're not syncing everything and if so if My Contacts is synced.
// If it isn't then the created contact can end up not in any groups that are
// currently synced and end up getting removed from the phone, which is really bad.
boolean syncingEverything = !"0".equals(Contacts.Settings.getSetting(mResolver, null,
Contacts.Settings.SYNC_EVERYTHING));
if (!syncingEverything) {
boolean syncingMyContacts = false;
Cursor c = mResolver.query(Groups.CONTENT_URI, new String[] { Groups.SHOULD_SYNC },
Groups.SYSTEM_ID + "=?", new String[] { Groups.GROUP_MY_CONTACTS }, null);
if (c != null) {
try {
if (c.moveToFirst()) {
syncingMyContacts = !"0".equals(c.getString(0));
}
} finally {
c.close();
}
}
if (!syncingMyContacts) {
// Not syncing My Contacts, so find a group that is being synced and stick
// the contact in there. We sort the list so at least all contacts
// will appear in the same group.
c = mResolver.query(Groups.CONTENT_URI, new String[] { Groups._ID },
Groups.SHOULD_SYNC + "!=0", null, Groups.DEFAULT_SORT_ORDER);
if (c != null) {
try {
if (c.moveToFirst()) {
People.addToGroup(mResolver, ContentUris.parseId(contactUri),
c.getLong(0));
}
} finally {
c.close();
}
}
}
}
}
// Handle the photo
if (mPhoto != null) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
mPhoto.compress(Bitmap.CompressFormat.JPEG, 75, stream);
Contacts.People.setPhotoData(getContentResolver(), contactUri, stream.toByteArray());
}
// Create the contact methods
int entryCount = ContactEntryAdapter.countEntries(mSections, false);
for (int i = 0; i < entryCount; i++) {
EditEntry entry = ContactEntryAdapter.getEntry(mSections, i, false);
if (entry.kind != EditEntry.KIND_CONTACT) {
values.clear();
if (entry.toValues(values)) {
// Only create the entry if there is data
entry.uri = mResolver.insert(
Uri.withAppendedPath(contactUri, entry.contentDirectory), values);
entry.id = ContentUris.parseId(entry.uri);
if (!People.CUSTOM_RINGTONE.equals(entry.column) &&
!People.SEND_TO_VOICEMAIL.equals(entry.column)) {
numValues++;
}
}
} else {
// Update the contact with any straggling data, like notes
data = entry.getData();
values.clear();
if (data != null && TextUtils.isGraphic(data)) {
values.put(entry.column, data);
mResolver.update(contactUri, values, null, null);
if (!People.CUSTOM_RINGTONE.equals(entry.column) &&
!People.SEND_TO_VOICEMAIL.equals(entry.column)) {
numValues++;
}
}
}
}
if (numValues == 0) {
mResolver.delete(contactUri, null, null);
setResult(RESULT_CANCELED);
} else {
mUri = contactUri;
Intent resultIntent = new Intent()
.setData(mUri)
.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
setResult(RESULT_OK, resultIntent);
Toast.makeText(this, R.string.contactCreatedToast, Toast.LENGTH_SHORT).show();
}
}
/**
* Build up the entries to display on the screen.
*
* @param extras the extras used to start this activity, may be null
*/
private void buildEntriesForEdit(Bundle extras) {
Cursor personCursor = mResolver.query(mUri, CONTACT_PROJECTION, null, null, null);
if (personCursor == null) {
Log.e(TAG, "invalid contact uri: " + mUri);
finish();
return;
} else if (!personCursor.moveToFirst()) {
Log.e(TAG, "invalid contact uri: " + mUri);
finish();
personCursor.close();
return;
}
// Clear out the old entries
int numSections = mSections.size();
for (int i = 0; i < numSections; i++) {
mSections.get(i).clear();
}
EditEntry entry;
// Name
mNameView.setText(personCursor.getString(CONTACT_NAME_COLUMN));
mNameView.addTextChangedListener(this);
// Photo
mPhoto = People.loadContactPhoto(this, mUri, 0, null);
if (mPhoto == null) {
setPhotoPresent(false);
} else {
setPhotoPresent(true);
mPhotoImageView.setImageBitmap(mPhoto);
}
// Organizations
Uri organizationsUri = Uri.withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY);
Cursor organizationsCursor = mResolver.query(organizationsUri, ORGANIZATIONS_PROJECTION,
null, null, null);
if (organizationsCursor != null) {
while (organizationsCursor.moveToNext()) {
int type = organizationsCursor.getInt(ORGANIZATIONS_TYPE_COLUMN);
String label = organizationsCursor.getString(ORGANIZATIONS_LABEL_COLUMN);
String company = organizationsCursor.getString(ORGANIZATIONS_COMPANY_COLUMN);
String title = organizationsCursor.getString(ORGANIZATIONS_TITLE_COLUMN);
long id = organizationsCursor.getLong(ORGANIZATIONS_ID_COLUMN);
Uri uri = ContentUris.withAppendedId(Organizations.CONTENT_URI, id);
// Add an organization entry
entry = EditEntry.newOrganizationEntry(this, label, type, company, title, uri, id);
entry.isPrimary = organizationsCursor.getLong(ORGANIZATIONS_ISPRIMARY_COLUMN) != 0;
mOrgEntries.add(entry);
}
organizationsCursor.close();
}
// Notes
if (!personCursor.isNull(CONTACT_NOTES_COLUMN)) {
entry = EditEntry.newNotesEntry(this, personCursor.getString(CONTACT_NOTES_COLUMN),
mUri);
mNoteEntries.add(entry);
}
// Ringtone
entry = EditEntry.newRingtoneEntry(this,
personCursor.getString(CONTACT_CUSTOM_RINGTONE_COLUMN), mUri);
mOtherEntries.add(entry);
// Send to voicemail
entry = EditEntry.newSendToVoicemailEntry(this,
personCursor.getString(CONTACT_SEND_TO_VOICEMAIL_COLUMN), mUri);
mOtherEntries.add(entry);
// Phonetic name
mPhoneticNameView.setText(personCursor.getString(CONTACT_PHONETIC_NAME_COLUMN));
mPhoneticNameView.addTextChangedListener(this);
personCursor.close();
// Build up the phone entries
Uri phonesUri = Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY);
Cursor phonesCursor = mResolver.query(phonesUri, PHONES_PROJECTION,
null, null, null);
if (phonesCursor != null) {
while (phonesCursor.moveToNext()) {
int type = phonesCursor.getInt(PHONES_TYPE_COLUMN);
String label = phonesCursor.getString(PHONES_LABEL_COLUMN);
String number = phonesCursor.getString(PHONES_NUMBER_COLUMN);
long id = phonesCursor.getLong(PHONES_ID_COLUMN);
boolean isPrimary = phonesCursor.getLong(PHONES_ISPRIMARY_COLUMN) != 0;
Uri uri = ContentUris.withAppendedId(phonesUri, id);
// Add a phone number entry
entry = EditEntry.newPhoneEntry(this, label, type, number, uri, id);
entry.isPrimary = isPrimary;
mPhoneEntries.add(entry);
// Keep track of which primary types have been added
if (type == Phones.TYPE_MOBILE) {
mMobilePhoneAdded = true;
}
}
phonesCursor.close();
}
// Build the contact method entries
Uri methodsUri = Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY);
Cursor methodsCursor = mResolver.query(methodsUri, METHODS_PROJECTION, null, null, null);
if (methodsCursor != null) {
while (methodsCursor.moveToNext()) {
int kind = methodsCursor.getInt(METHODS_KIND_COLUMN);
String label = methodsCursor.getString(METHODS_LABEL_COLUMN);
String data = methodsCursor.getString(METHODS_DATA_COLUMN);
String auxData = methodsCursor.getString(METHODS_AUX_DATA_COLUMN);
int type = methodsCursor.getInt(METHODS_TYPE_COLUMN);
long id = methodsCursor.getLong(METHODS_ID_COLUMN);
boolean isPrimary = methodsCursor.getLong(METHODS_ISPRIMARY_COLUMN) != 0;
Uri uri = ContentUris.withAppendedId(methodsUri, id);
switch (kind) {
case Contacts.KIND_EMAIL: {
entry = EditEntry.newEmailEntry(this, label, type, data, uri, id);
entry.isPrimary = isPrimary;
mEmailEntries.add(entry);
if (isPrimary) {
mPrimaryEmailAdded = true;
}
break;
}
case Contacts.KIND_POSTAL: {
entry = EditEntry.newPostalEntry(this, label, type, data, uri, id);
entry.isPrimary = isPrimary;
mPostalEntries.add(entry);
break;
}
case Contacts.KIND_IM: {
Object protocolObj = ContactMethods.decodeImProtocol(auxData);
if (protocolObj == null) {
// Invalid IM protocol, log it then ignore.
Log.e(TAG, "Couldn't decode IM protocol: " + auxData);
continue;
} else {
if (protocolObj instanceof Number) {
int protocol = ((Number) protocolObj).intValue();
entry = EditEntry.newImEntry(this,
getLabelsForKind(this, Contacts.KIND_IM)[protocol], protocol,
data, uri, id);
} else {
entry = EditEntry.newImEntry(this, protocolObj.toString(), -1, data,
uri, id);
}
mImEntries.add(entry);
}
break;
}
}
}
methodsCursor.close();
}
// Add values from the extras, if there are any
if (extras != null) {
addFromExtras(extras, phonesUri, methodsUri);
}
// Add the base types if needed
if (!mMobilePhoneAdded) {
entry = EditEntry.newPhoneEntry(this,
Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY),
DEFAULT_PHONE_TYPE);
mPhoneEntries.add(entry);
}
if (!mPrimaryEmailAdded) {
entry = EditEntry.newEmailEntry(this,
Uri.withAppendedPath(mUri, People.ContactMethods.CONTENT_DIRECTORY),
DEFAULT_EMAIL_TYPE);
entry.isPrimary = true;
mEmailEntries.add(entry);
}
mContactChanged = false;
}
/**
* Build the list of EditEntries for full mode insertions.
*
* @param extras the extras used to start this activity, may be null
*/
private void buildEntriesForInsert(Bundle extras) {
// Clear out the old entries
int numSections = mSections.size();
for (int i = 0; i < numSections; i++) {
mSections.get(i).clear();
}
EditEntry entry;
// Check the intent extras
if (extras != null) {
addFromExtras(extras, null, null);
}
// Photo
mPhotoImageView.setImageResource(R.drawable.ic_contact_picture);
// Add the base entries if they're not already present
if (!mMobilePhoneAdded) {
entry = EditEntry.newPhoneEntry(this, null, Phones.TYPE_MOBILE);
entry.isPrimary = true;
mPhoneEntries.add(entry);
}
if (!mPrimaryEmailAdded) {
entry = EditEntry.newEmailEntry(this, null, DEFAULT_EMAIL_TYPE);
entry.isPrimary = true;
mEmailEntries.add(entry);
}
// Ringtone
entry = EditEntry.newRingtoneEntry(this, null, mUri);
mOtherEntries.add(entry);
// Send to voicemail
entry = EditEntry.newSendToVoicemailEntry(this, "0", mUri);
mOtherEntries.add(entry);
}
private void addFromExtras(Bundle extras, Uri phonesUri, Uri methodsUri) {
EditEntry entry;
// Read the name from the bundle
CharSequence name = extras.getCharSequence(Insert.NAME);
if (name != null && TextUtils.isGraphic(name)) {
mNameView.setText(name);
}
// Read the phonetic name from the bundle
CharSequence phoneticName = extras.getCharSequence(Insert.PHONETIC_NAME);
if (!TextUtils.isEmpty(phoneticName)) {
mPhoneticNameView.setText(phoneticName);
}
// Postal entries from extras
CharSequence postal = extras.getCharSequence(Insert.POSTAL);
int postalType = extras.getInt(Insert.POSTAL_TYPE, INVALID_TYPE);
if (!TextUtils.isEmpty(postal) && postalType == INVALID_TYPE) {
postalType = DEFAULT_POSTAL_TYPE;
}
if (postalType != INVALID_TYPE) {
entry = EditEntry.newPostalEntry(this, null, postalType, postal.toString(),
methodsUri, 0);
entry.isPrimary = extras.getBoolean(Insert.POSTAL_ISPRIMARY);
mPostalEntries.add(entry);
}
// Email entries from extras
addEmailFromExtras(extras, methodsUri, Insert.EMAIL, Insert.EMAIL_TYPE,
Insert.EMAIL_ISPRIMARY);
addEmailFromExtras(extras, methodsUri, Insert.SECONDARY_EMAIL, Insert.SECONDARY_EMAIL_TYPE,
null);
addEmailFromExtras(extras, methodsUri, Insert.TERTIARY_EMAIL, Insert.TERTIARY_EMAIL_TYPE,
null);
// Phone entries from extras
addPhoneFromExtras(extras, phonesUri, Insert.PHONE, Insert.PHONE_TYPE,
Insert.PHONE_ISPRIMARY);
addPhoneFromExtras(extras, phonesUri, Insert.SECONDARY_PHONE, Insert.SECONDARY_PHONE_TYPE,
null);
addPhoneFromExtras(extras, phonesUri, Insert.TERTIARY_PHONE, Insert.TERTIARY_PHONE_TYPE,
null);
// IM entries from extras
CharSequence imHandle = extras.getCharSequence(Insert.IM_HANDLE);
CharSequence imProtocol = extras.getCharSequence(Insert.IM_PROTOCOL);
if (imHandle != null && imProtocol != null) {
Object protocolObj = ContactMethods.decodeImProtocol(imProtocol.toString());
if (protocolObj instanceof Number) {
int protocol = ((Number) protocolObj).intValue();
entry = EditEntry.newImEntry(this,
getLabelsForKind(this, Contacts.KIND_IM)[protocol], protocol,
imHandle.toString(), methodsUri, 0);
} else {
entry = EditEntry.newImEntry(this, protocolObj.toString(), -1, imHandle.toString(),
methodsUri, 0);
}
entry.isPrimary = extras.getBoolean(Insert.IM_ISPRIMARY);
mImEntries.add(entry);
}
}
private void addEmailFromExtras(Bundle extras, Uri methodsUri, String emailField,
String typeField, String primaryField) {
CharSequence email = extras.getCharSequence(emailField);
// Correctly handle String in typeField as TYPE_CUSTOM
int emailType = INVALID_TYPE;
String customLabel = null;
if(extras.get(typeField) instanceof String) {
emailType = ContactMethods.TYPE_CUSTOM;
customLabel = extras.getString(typeField);
} else {
emailType = extras.getInt(typeField, INVALID_TYPE);
}
if (!TextUtils.isEmpty(email) && emailType == INVALID_TYPE) {
emailType = DEFAULT_EMAIL_TYPE;
mPrimaryEmailAdded = true;
}
if (emailType != INVALID_TYPE) {
EditEntry entry = EditEntry.newEmailEntry(this, customLabel, emailType, email.toString(),
methodsUri, 0);
entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
mEmailEntries.add(entry);
// Keep track of which primary types have been added
if (entry.isPrimary) {
mPrimaryEmailAdded = true;
}
}
}
private void addPhoneFromExtras(Bundle extras, Uri phonesUri, String phoneField,
String typeField, String primaryField) {
CharSequence phoneNumber = extras.getCharSequence(phoneField);
// Correctly handle String in typeField as TYPE_CUSTOM
int phoneType = INVALID_TYPE;
String customLabel = null;
if(extras.get(typeField) instanceof String) {
phoneType = Phones.TYPE_CUSTOM;
customLabel = extras.getString(typeField);
} else {
phoneType = extras.getInt(typeField, INVALID_TYPE);
}
if (!TextUtils.isEmpty(phoneNumber) && phoneType == INVALID_TYPE) {
phoneType = DEFAULT_PHONE_TYPE;
}
if (phoneType != INVALID_TYPE) {
EditEntry entry = EditEntry.newPhoneEntry(this, customLabel, phoneType,
phoneNumber.toString(), phonesUri, 0);
entry.isPrimary = (primaryField == null) ? false : extras.getBoolean(primaryField);
mPhoneEntries.add(entry);
// Keep track of which primary types have been added
if (phoneType == Phones.TYPE_MOBILE) {
mMobilePhoneAdded = true;
}
}
}
/**
* Removes all existing views, builds new ones for all the entries, and adds them.
*/
private void buildViews() {
// Remove existing views
final LinearLayout layout = mLayout;
layout.removeAllViews();
buildViewsForSection(layout, mPhoneEntries,
R.string.listSeparatorCallNumber_edit, SECTION_PHONES);
buildViewsForSection(layout, mEmailEntries,
R.string.listSeparatorSendEmail_edit, SECTION_EMAIL);
buildViewsForSection(layout, mImEntries,
R.string.listSeparatorSendIm_edit, SECTION_IM);
buildViewsForSection(layout, mPostalEntries,
R.string.listSeparatorMapAddress_edit, SECTION_POSTAL);
buildViewsForSection(layout, mOrgEntries,
R.string.listSeparatorOrganizations, SECTION_ORG);
buildViewsForSection(layout, mNoteEntries,
R.string.label_notes, SECTION_NOTE);
buildOtherViews(layout, mOtherEntries);
}
/**
* Builds the views for a specific section.
*
* @param layout the container
* @param section the section to build the views for
*/
private void buildViewsForSection(final LinearLayout layout, ArrayList<EditEntry> section,
int separatorResource, int sectionType) {
View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
layout.addView(divider);
// Count up undeleted children
int activeChildren = 0;
for (int i = section.size() - 1; i >= 0; i--) {
EditEntry entry = section.get(i);
if (!entry.isDeleted) {
activeChildren++;
}
}
// Build the correct group header based on undeleted children
ViewGroup header;
if (activeChildren == 0) {
header = (ViewGroup) mInflater.inflate(R.layout.edit_separator_alone, layout, false);
} else {
header = (ViewGroup) mInflater.inflate(R.layout.edit_separator, layout, false);
}
// Because we're emulating a ListView, we need to handle focus changes
// with some additional logic.
header.setOnFocusChangeListener(this);
TextView text = (TextView) header.findViewById(R.id.text);
text.setText(getText(separatorResource));
// Force TextView to always default color if we have children. This makes sure
// we don't change color when parent is pressed.
if (activeChildren > 0) {
ColorStateList stateList = text.getTextColors();
text.setTextColor(stateList.getDefaultColor());
}
View addView = header.findViewById(R.id.separator);
addView.setTag(Integer.valueOf(sectionType));
addView.setOnClickListener(this);
// Build views for the current section
for (EditEntry entry : section) {
entry.activity = this; // this could be null from when the state is restored
if (!entry.isDeleted) {
View view = buildViewForEntry(entry);
header.addView(view);
}
}
layout.addView(header);
}
private void buildOtherViews(final LinearLayout layout, ArrayList<EditEntry> section) {
// Build views for the current section, putting a divider between each one
for (EditEntry entry : section) {
View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
layout.addView(divider);
entry.activity = this; // this could be null from when the state is restored
View view = buildViewForEntry(entry);
view.setOnClickListener(this);
layout.addView(view);
}
View divider = mInflater.inflate(R.layout.edit_divider, layout, false);
layout.addView(divider);
}
/**
* Builds a view to display an EditEntry.
*
* @param entry the entry to display
* @return a view that will display the given entry
*/
/* package */ View buildViewForEntry(final EditEntry entry) {
// Look for any existing entered text, and save it if found
if (entry.view != null && entry.syncDataWithView) {
String enteredText = ((TextView) entry.view.findViewById(R.id.data))
.getText().toString();
if (!TextUtils.isEmpty(enteredText)) {
entry.data = enteredText;
}
}
// Build a new view
final ViewGroup parent = mLayout;
View view;
// Because we're emulating a ListView, we might need to handle focus changes
// with some additional logic.
if (entry.kind == Contacts.KIND_ORGANIZATION) {
view = mInflater.inflate(R.layout.edit_contact_entry_org, parent, false);
} else if (isOtherEntry(entry, People.CUSTOM_RINGTONE)) {
view = mInflater.inflate(R.layout.edit_contact_entry_ringtone, parent, false);
view.setOnFocusChangeListener(this);
} else if (isOtherEntry(entry, People.SEND_TO_VOICEMAIL)) {
view = mInflater.inflate(R.layout.edit_contact_entry_voicemail, parent, false);
view.setOnFocusChangeListener(this);
} else if (!entry.isStaticLabel) {
view = mInflater.inflate(R.layout.edit_contact_entry, parent, false);
} else {
view = mInflater.inflate(R.layout.edit_contact_entry_static_label, parent, false);
}
entry.view = view;
// Set the entry as the tag so we can find it again later given just the view
view.setTag(entry);
// Bind the label
entry.bindLabel(this);
// Bind data
TextView data = (TextView) view.findViewById(R.id.data);
TextView data2 = (TextView) view.findViewById(R.id.data2);
if (data instanceof Button) {
data.setOnClickListener(this);
}
if (data.length() == 0) {
if (entry.syncDataWithView) {
// If there is already data entered don't overwrite it
data.setText(entry.data);
} else {
fillViewData(entry);
}
}
if (data2 != null && data2.length() == 0) {
// If there is already data entered don't overwrite it
data2.setText(entry.data2);
}
data.setHint(entry.hint);
if (data2 != null) data2.setHint(entry.hint2);
if (entry.lines > 1) {
data.setLines(entry.lines);
data.setMaxLines(entry.maxLines);
if (data2 != null) {
data2.setLines(entry.lines);
data2.setMaxLines(entry.maxLines);
}
}
int contentType = entry.contentType;
if (contentType != EditorInfo.TYPE_NULL) {
data.setInputType(contentType);
if (data2 != null) {
data2.setInputType(contentType);
}
if ((contentType&EditorInfo.TYPE_MASK_CLASS)
== EditorInfo.TYPE_CLASS_PHONE) {
data.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
if (data2 != null) {
data2.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
}
}
}
// Give focus to children as requested, possibly after a configuration change
View focusChild = view.findViewById(entry.requestFocusId);
if (focusChild != null) {
focusChild.requestFocus();
if (focusChild instanceof EditText) {
((EditText) focusChild).setSelection(entry.requestCursor);
}
}
// Reset requested focus values
entry.requestFocusId = View.NO_ID;
entry.requestCursor = 0;
// Connect listeners up to watch for changed values.
if (data instanceof EditText) {
data.addTextChangedListener(this);
}
if (data2 instanceof EditText) {
data2.addTextChangedListener(this);
}
// Hook up the delete button
View delete = view.findViewById(R.id.delete);
if (delete != null) delete.setOnClickListener(this);
return view;
}
private void fillViewData(final EditEntry entry) {
if (isOtherEntry(entry, People.CUSTOM_RINGTONE)) {
updateRingtoneView(entry);
} else if (isOtherEntry(entry, People.SEND_TO_VOICEMAIL)) {
CheckBox checkBox = (CheckBox) entry.view.findViewById(R.id.checkbox);
boolean sendToVoicemail = false;
if (entry.data != null) {
sendToVoicemail = (Integer.valueOf(entry.data) == 1);
}
checkBox.setChecked(sendToVoicemail);
}
}
/**
* Handles the results from the label change picker.
*/
private final class LabelPickedListener implements DialogInterface.OnClickListener {
EditEntry mEntry;
String[] mLabels;
public LabelPickedListener(EditEntry entry, String[] labels) {
mEntry = entry;
mLabels = labels;
}
public void onClick(DialogInterface dialog, int which) {
// TODO: Use a managed dialog
if (mEntry.kind != Contacts.KIND_IM) {
final int type = getTypeFromLabelPosition(mLabels, which);
if (type == ContactMethods.TYPE_CUSTOM) {
createCustomPicker(mEntry, null);
} else {
mEntry.setLabel(EditContactActivity.this, type, mLabels[which]);
mContactChanged = true;
}
} else {
mEntry.setLabel(EditContactActivity.this, which, mLabels[which]);
mContactChanged = true;
}
}
}
/**
* A basic structure with the data for a contact entry in the list.
*/
private static final class EditEntry extends ContactEntryAdapter.Entry implements Parcelable {
// These aren't stuffed into the parcel
public EditContactActivity activity;
public View view;
// These are stuffed into the parcel
public String hint;
public String hint2;
public String column;
public String contentDirectory;
public String data2;
public int contentType;
public int type;
/**
* If 0 or 1, setSingleLine will be called. If negative, setSingleLine
* will not be called.
*/
public int lines = 1;
public boolean isPrimary;
public boolean isDeleted = false;
public boolean isStaticLabel = false;
public boolean syncDataWithView = true;
/**
* Request focus on the child of this {@link EditEntry} found using
* {@link View#findViewById(int)}. This value should be reset to
* {@link View#NO_ID} after each use.
*/
public int requestFocusId = View.NO_ID;
/**
* If the {@link #requestFocusId} is an {@link EditText}, this value
* indicates the requested cursor position placement.
*/
public int requestCursor = 0;
private EditEntry() {
// only used by CREATOR
}
public EditEntry(EditContactActivity activity) {
this.activity = activity;
}
public EditEntry(EditContactActivity activity, String label,
int type, String data, Uri uri, long id) {
this.activity = activity;
this.isPrimary = false;
this.label = label;
this.type = type;
this.data = data;
this.uri = uri;
this.id = id;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel parcel, int flags) {
// Make sure to read data from the input field, if anything is entered
data = getData();
// Write in our own fields.
parcel.writeString(hint);
parcel.writeString(hint2);
parcel.writeString(column);
parcel.writeString(contentDirectory);
parcel.writeString(data2);
parcel.writeInt(contentType);
parcel.writeInt(type);
parcel.writeInt(lines);
parcel.writeInt(isPrimary ? 1 : 0);
parcel.writeInt(isDeleted ? 1 : 0);
parcel.writeInt(isStaticLabel ? 1 : 0);
parcel.writeInt(syncDataWithView ? 1 : 0);
// Write in the fields from Entry
super.writeToParcel(parcel);
}
public static final Parcelable.Creator<EditEntry> CREATOR =
new Parcelable.Creator<EditEntry>() {
public EditEntry createFromParcel(Parcel in) {
EditEntry entry = new EditEntry();
// Read out our own fields
entry.hint = in.readString();
entry.hint2 = in.readString();
entry.column = in.readString();
entry.contentDirectory = in.readString();
entry.data2 = in.readString();
entry.contentType = in.readInt();
entry.type = in.readInt();
entry.lines = in.readInt();
entry.isPrimary = in.readInt() == 1;
entry.isDeleted = in.readInt() == 1;
entry.isStaticLabel = in.readInt() == 1;
entry.syncDataWithView = in.readInt() == 1;
// Read out the fields from Entry
entry.readFromParcel(in);
return entry;
}
public EditEntry[] newArray(int size) {
return new EditEntry[size];
}
};
public void setLabel(Context context, int typeIn, String labelIn) {
type = typeIn;
label = labelIn;
if (view != null) {
bindLabel(context);
}
}
public void bindLabel(Context context) {
TextView v = (TextView) view.findViewById(R.id.label);
if (isStaticLabel) {
v.setText(label);
return;
}
switch (kind) {
case Contacts.KIND_PHONE: {
v.setText(Phones.getDisplayLabel(context, type, label));
break;
}
case Contacts.KIND_IM: {
v.setText(getLabelsForKind(activity, kind)[type]);
break;
}
case Contacts.KIND_ORGANIZATION: {
v.setText(Organizations.getDisplayLabel(activity, type, label));
break;
}
default: {
v.setText(Contacts.ContactMethods.getDisplayLabel(context, kind, type, label));
if (kind == Contacts.KIND_POSTAL) {
v.setMaxLines(3);
}
break;
}
}
v.setOnClickListener(activity);
}
/**
* Returns the data for the entry
* @return the data for the entry
*/
public String getData() {
if (view != null && syncDataWithView) {
CharSequence text = ((TextView) view.findViewById(R.id.data)).getText();
if (text != null) {
return text.toString();
}
}
if (data != null) {
return data.toString();
}
return null;
}
/**
* Dumps the entry into a HashMap suitable for passing to the database.
*
* @param values the HashMap to fill in.
* @return true if the value should be saved, false otherwise
*/
public boolean toValues(ContentValues values) {
boolean success = false;
String labelString = null;
// Save the type and label
if (view != null) {
// Read the possibly updated label from the text field
labelString = ((TextView) view.findViewById(R.id.label)).getText().toString();
}
switch (kind) {
case Contacts.KIND_PHONE:
if (type != Phones.TYPE_CUSTOM) {
labelString = null;
}
values.put(Phones.LABEL, labelString);
values.put(Phones.TYPE, type);
break;
case Contacts.KIND_EMAIL:
if (type != ContactMethods.TYPE_CUSTOM) {
labelString = null;
}
values.put(ContactMethods.LABEL, labelString);
values.put(ContactMethods.KIND, kind);
values.put(ContactMethods.TYPE, type);
break;
case Contacts.KIND_IM:
values.put(ContactMethods.KIND, kind);
values.put(ContactMethods.TYPE, ContactMethods.TYPE_OTHER);
values.putNull(ContactMethods.LABEL);
if (type != -1) {
values.put(ContactMethods.AUX_DATA,
ContactMethods.encodePredefinedImProtocol(type));
} else {
values.put(ContactMethods.AUX_DATA,
ContactMethods.encodeCustomImProtocol(label.toString()));
}
break;
case Contacts.KIND_POSTAL:
if (type != ContactMethods.TYPE_CUSTOM) {
labelString = null;
}
values.put(ContactMethods.LABEL, labelString);
values.put(ContactMethods.KIND, kind);
values.put(ContactMethods.TYPE, type);
break;
case Contacts.KIND_ORGANIZATION:
if (type != ContactMethods.TYPE_CUSTOM) {
labelString = null;
}
values.put(ContactMethods.LABEL, labelString);
values.put(ContactMethods.TYPE, type);
// Save the title
if (view != null) {
// Read the possibly updated data from the text field
data2 = ((TextView) view.findViewById(R.id.data2)).getText().toString();
}
if (!TextUtils.isGraphic(data2)) {
values.putNull(Organizations.TITLE);
} else {
values.put(Organizations.TITLE, data2.toString());
success = true;
}
break;
default:
Log.w(TAG, "unknown kind " + kind);
values.put(ContactMethods.LABEL, labelString);
values.put(ContactMethods.KIND, kind);
values.put(ContactMethods.TYPE, type);
break;
}
// Only set the ISPRIMARY flag if part of the incoming data. This is because the
// ContentProvider will try finding a new primary when setting to false, meaning
// it's possible to lose primary altogether as we walk down the list. If this editor
// implements editing of primaries in the future, this will need to be revisited.
if (isPrimary) {
values.put(ContactMethods.ISPRIMARY, 1);
}
// Save the data
if (view != null && syncDataWithView) {
// Read the possibly updated data from the text field
data = ((TextView) view.findViewById(R.id.data)).getText().toString();
}
if (!TextUtils.isGraphic(data)) {
values.putNull(column);
return success;
} else {
values.put(column, data.toString());
return true;
}
}
/**
* Create a new empty organization entry
*/
public static final EditEntry newOrganizationEntry(EditContactActivity activity,
Uri uri, int type) {
return newOrganizationEntry(activity, null, type, null, null, uri, 0);
}
/**
* Create a new company entry with the given data.
*/
public static final EditEntry newOrganizationEntry(EditContactActivity activity,
String label, int type, String company, String title, Uri uri, long id) {
EditEntry entry = new EditEntry(activity, label, type, company, uri, id);
entry.hint = activity.getString(R.string.ghostData_company);
entry.hint2 = activity.getString(R.string.ghostData_title);
entry.data2 = title;
entry.column = Organizations.COMPANY;
entry.contentDirectory = Organizations.CONTENT_DIRECTORY;
entry.kind = Contacts.KIND_ORGANIZATION;
entry.contentType = EditorInfo.TYPE_CLASS_TEXT
| EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
return entry;
}
/**
* Create a new notes entry with the given data.
*/
public static final EditEntry newNotesEntry(EditContactActivity activity,
String data, Uri uri) {
EditEntry entry = new EditEntry(activity);
entry.label = activity.getString(R.string.label_notes);
entry.hint = activity.getString(R.string.ghostData_notes);
entry.data = data;
entry.uri = uri;
entry.column = People.NOTES;
entry.maxLines = 10;
entry.lines = 2;
entry.id = 0;
entry.kind = KIND_CONTACT;
entry.contentType = EditorInfo.TYPE_CLASS_TEXT
| EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
| EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
entry.isStaticLabel = true;
return entry;
}
/**
* Create a new ringtone entry with the given data.
*/
public static final EditEntry newRingtoneEntry(EditContactActivity activity,
String data, Uri uri) {
EditEntry entry = new EditEntry(activity);
entry.label = activity.getString(R.string.label_ringtone);
entry.data = data;
entry.uri = uri;
entry.column = People.CUSTOM_RINGTONE;
entry.kind = KIND_CONTACT;
entry.isStaticLabel = true;
entry.syncDataWithView = false;
entry.lines = -1;
return entry;
}
/**
* Create a new send-to-voicemail entry with the given data.
*/
public static final EditEntry newSendToVoicemailEntry(EditContactActivity activity,
String data, Uri uri) {
EditEntry entry = new EditEntry(activity);
entry.label = activity.getString(R.string.actionIncomingCall);
entry.data = data;
entry.uri = uri;
entry.column = People.SEND_TO_VOICEMAIL;
entry.kind = KIND_CONTACT;
entry.isStaticLabel = true;
entry.syncDataWithView = false;
entry.lines = -1;
return entry;
}
/**
* Create a new empty email entry
*/
public static final EditEntry newPhoneEntry(EditContactActivity activity,
Uri uri, int type) {
return newPhoneEntry(activity, null, type, null, uri, 0);
}
/**
* Create a new phone entry with the given data.
*/
public static final EditEntry newPhoneEntry(EditContactActivity activity,
String label, int type, String data, Uri uri,
long id) {
EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
entry.hint = activity.getString(R.string.ghostData_phone);
entry.column = People.Phones.NUMBER;
entry.contentDirectory = People.Phones.CONTENT_DIRECTORY;
entry.kind = Contacts.KIND_PHONE;
entry.contentType = EditorInfo.TYPE_CLASS_PHONE;
return entry;
}
/**
* Create a new empty email entry
*/
public static final EditEntry newEmailEntry(EditContactActivity activity,
Uri uri, int type) {
return newEmailEntry(activity, null, type, null, uri, 0);
}
/**
* Create a new email entry with the given data.
*/
public static final EditEntry newEmailEntry(EditContactActivity activity,
String label, int type, String data, Uri uri,
long id) {
EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
entry.hint = activity.getString(R.string.ghostData_email);
entry.column = ContactMethods.DATA;
entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
entry.kind = Contacts.KIND_EMAIL;
entry.contentType = EditorInfo.TYPE_CLASS_TEXT
| EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
return entry;
}
/**
* Create a new empty postal address entry
*/
public static final EditEntry newPostalEntry(EditContactActivity activity,
Uri uri, int type) {
return newPostalEntry(activity, null, type, null, uri, 0);
}
/**
* Create a new postal address entry with the given data.
*
* @param label label for the item, from the db not the display label
* @param type the type of postal address
* @param data the starting data for the entry, may be null
* @param uri the uri for the entry if it already exists, may be null
* @param id the id for the entry if it already exists, 0 it it doesn't
* @return the new EditEntry
*/
public static final EditEntry newPostalEntry(EditContactActivity activity,
String label, int type, String data, Uri uri, long id) {
EditEntry entry = new EditEntry(activity, label, type, data, uri, id);
entry.hint = activity.getString(R.string.ghostData_postal);
entry.column = ContactMethods.DATA;
entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
entry.kind = Contacts.KIND_POSTAL;
entry.contentType = EditorInfo.TYPE_CLASS_TEXT
| EditorInfo.TYPE_TEXT_VARIATION_POSTAL_ADDRESS
| EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS
| EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
entry.maxLines = 4;
entry.lines = 2;
return entry;
}
/**
* Create a new IM address entry
*/
public static final EditEntry newImEntry(EditContactActivity activity,
Uri uri, int type) {
return newImEntry(activity, null, type, null, uri, 0);
}
/**
* Create a new IM address entry with the given data.
*
* @param label label for the item, from the db not the display label
* @param protocol the type used
* @param data the starting data for the entry, may be null
* @param uri the uri for the entry if it already exists, may be null
* @param id the id for the entry if it already exists, 0 it it doesn't
* @return the new EditEntry
*/
public static final EditEntry newImEntry(EditContactActivity activity,
String label, int protocol, String data, Uri uri, long id) {
EditEntry entry = new EditEntry(activity, label, protocol, data, uri, id);
entry.hint = activity.getString(R.string.ghostData_im);
entry.column = ContactMethods.DATA;
entry.contentDirectory = People.ContactMethods.CONTENT_DIRECTORY;
entry.kind = Contacts.KIND_IM;
entry.contentType = EditorInfo.TYPE_CLASS_TEXT
| EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
return entry;
}
}
public void afterTextChanged(Editable s) {
// Someone edited a text field, so assume this contact is changed
mContactChanged = true;
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Do nothing; editing handled by afterTextChanged()
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Do nothing; editing handled by afterTextChanged()
}
public void onFocusChange(View v, boolean hasFocus) {
// Because we're emulating a ListView, we need to setSelected() for
// views as they are focused.
v.setSelected(hasFocus);
}
}