blob: 32b55ef64ff2dfab8e261efcded12ac7f324450e [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.AGGREGATE_DISPLAY_NAME_COLUMN;
import static com.android.contacts.ContactEntryAdapter.AGGREGATE_PROJECTION;
import static com.android.contacts.ContactEntryAdapter.AGGREGATE_STARRED_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_ID_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_PACKAGE_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_MIMETYPE_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_IS_PRIMARY_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_IS_SUPER_PRIMARY_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_1_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_2_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_3_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_4_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_5_COLUMN;
import static com.android.contacts.ContactEntryAdapter.DATA_9_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.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Aggregates;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.Postal;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Data;
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;
// TODO: Much of this class has been commented out as a starting place for transition to new data
// model. It will be added back as we progress.
/**
* 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;
// 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 = Phone.TYPE_MOBILE;
/** The default type for an email that is added via an intent */
private static final int DEFAULT_EMAIL_TYPE = Email.TYPE_HOME;
/** The default type for a postal address that is added via an intent */
private static final int DEFAULT_POSTAL_TYPE = Postal.TYPE_HOME;
private int mState; // saved across instances
private boolean mInsert; // saved across instances
private Uri mUri; // saved across instances
private Uri mAggDataUri;
/** In insert mode this is the photo */
private Bitmap mPhoto; // saved across instances
private boolean mPhotoChanged = false; // saved across instances
private EditText mNameView;
private Uri mStructuredNameUri;
private Uri mPhotoDataUri;
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;
/** List of all the group names */
private CharSequence[] mGroups;
/** Is this contact part of the group */
private boolean[] mInTheGroup;
/*
private static final String[] GROUP_ID_PROJECTION = new String[] {
Groups._ID,
};
private static final String[] GROUPMEMBERSHIP_ID_PROJECTION = new String[] {
GroupMembership._ID,
};
*/
// 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;
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_group: {
EditEntry entry = findEntryForView(v);
doPickGroup(entry);
break;
}
*/
case R.id.separator: {
// Someone clicked on a section header, so handle add action
// TODO: Data addition is still being hashed out.
/*
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 = getLabelsForMimetype(this, entry.mimetype);
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();
mAggDataUri = Uri.withAppendedPath(mUri, "data");
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;
}
}
}
@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];
}
// TODO When this gets brought back we'll need to use the new TypePrecedence class instead of
// the older local TYPE_PRECEDENCE* contstants.
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, Data.CONTENT_URI,
nextType);
mPhoneEntries.add(entry);
break;
}
case SECTION_EMAIL: {
// Try figuring out which type to insert next
int nextType = guessNextType(mEmailEntries, TYPE_PRECEDENCE_EMAIL);
entry = EditEntry.newEmailEntry(EditContactActivity.this, Data.CONTENT_URI,
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, Data.CONTENT_URI, nextType);
mImEntries.add(entry);
break;
}
case SECTION_POSTAL: {
int nextType = guessNextType(mPostalEntries, TYPE_PRECEDENCE_POSTAL);
entry = EditEntry.newPostalEntry(EditContactActivity.this, Data.CONTENT_URI,
nextType);
mPostalEntries.add(entry);
break;
}
case SECTION_ORG: {
int nextType = guessNextType(mOrgEntries, TYPE_PRECEDENCE_ORG);
entry = EditEntry.newOrganizationEntry(EditContactActivity.this, Data.CONTENT_URI,
nextType);
mOrgEntries.add(entry);
break;
}
case SECTION_NOTE: {
entry = EditEntry.newNotesEntry(EditContactActivity.this, Data.CONTENT_URI);
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 populateGroups() {
// Create a list of all the groups
Cursor cursor = mResolver.query(Groups.CONTENT_URI, ContactsListActivity.GROUPS_PROJECTION,
null, null, Groups.DEFAULT_SORT_ORDER);
try {
ArrayList<Long> ids = new ArrayList<Long>();
ArrayList<String> items = new ArrayList<String>();
while (cursor.moveToNext()) {
String systemId = cursor.getString(ContactsListActivity.GROUPS_COLUMN_INDEX_SYSTEM_ID);
String name = cursor.getString(ContactsListActivity.GROUPS_COLUMN_INDEX_NAME);
if (systemId != null || Groups.GROUP_MY_CONTACTS.equals(systemId)) {
continue;
}
if (!TextUtils.isEmpty(name)) {
ids.add(new Long(cursor.getLong(ContactsListActivity.GROUPS_COLUMN_INDEX_SYSTEM_ID)));
items.add(name);
}
}
mGroups = items.toArray(new CharSequence[items.size()]);
mInTheGroup = new boolean[items.size()];
} finally {
cursor.close();
}
if (mGroups != null) {
// Go through the groups for this member and update the list
final Uri groupsUri = Uri.withAppendedPath(mUri, GroupMembership.CONTENT_DIRECTORY);
Cursor groupCursor = null;
try {
groupCursor = mResolver.query(groupsUri, ContactsListActivity.GROUPS_PROJECTION,
null, null, Groups.DEFAULT_SORT_ORDER);
} catch (IllegalArgumentException e) {
// Contact is new, so we don't need to do any work.
}
if (groupCursor != null) {
try {
while (groupCursor.moveToNext()) {
String systemId = groupCursor.getString(ContactsListActivity.GROUPS_COLUMN_INDEX_SYSTEM_ID);
String name = groupCursor.getString(ContactsListActivity.GROUPS_COLUMN_INDEX_NAME);
if (systemId != null || Groups.GROUP_MY_CONTACTS.equals(systemId)) {
continue;
}
if (!TextUtils.isEmpty(name)) {
for (int i = 0; i < mGroups.length; i++) {
if (name.equals(mGroups[i])) {
mInTheGroup[i] = true;
break;
}
}
}
}
} finally {
groupCursor.close();
}
}
}
}
private String generateGroupList() {
StringBuilder groupList = new StringBuilder();
for (int i = 0; mGroups != null && i < mGroups.length; i++) {
if (mInTheGroup[i]) {
if (groupList.length() == 0) {
groupList.append(mGroups[i]);
} else {
groupList.append(getString(R.string.group_list, mGroups[i]));
}
}
}
return groupList.length() > 0 ? groupList.toString() : null;
}
private void doPickGroup(EditEntry entry) {
if (mGroups != null) {
GroupDialogListener listener = new GroupDialogListener(this, entry);
new AlertDialog.Builder(EditContactActivity.this)
.setTitle(R.string.label_groups)
.setMultiChoiceItems(mGroups, mInTheGroup, listener)
.setPositiveButton(android.R.string.ok, listener)
.setNegativeButton(android.R.string.cancel, null)
.show();
}
}
*/
/** Handles the clicks in the groups dialog */
/*
private static final class GroupDialogListener implements DialogInterface.OnClickListener,
DialogInterface.OnMultiChoiceClickListener {
private EditContactActivity mEditContactActivity;
private EditEntry mEntry;
private boolean[] mInTheGroup;
public GroupDialogListener(EditContactActivity editContactActivity, EditEntry entry) {
mEditContactActivity = editContactActivity;
mEntry = entry;
mInTheGroup = editContactActivity.mInTheGroup.clone();
}
// Called when the dialog's ok button is clicked
public void onClick(DialogInterface dialog, int which) {
mEditContactActivity.mInTheGroup = mInTheGroup;
mEntry.data = mEditContactActivity.generateGroupList();
mEditContactActivity.updateDataView(mEntry, mEntry.data);
}
// Called when each group is clicked
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
mInTheGroup[which] = isChecked;
}
}
*/
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[] getLabelsForMimetype(Context context, String mimetype) {
final Resources resources = context.getResources();
if (mimetype.equals(Phone.CONTENT_ITEM_TYPE)) {
return resources.getStringArray(android.R.array.phoneTypes);
} else if (mimetype.equals(Email.CONTENT_ITEM_TYPE)) {
return resources.getStringArray(android.R.array.emailAddressTypes);
} else if (mimetype.equals(Postal.CONTENT_ITEM_TYPE)) {
return resources.getStringArray(android.R.array.postalAddressTypes);
} else if (mimetype.equals(Im.CONTENT_ITEM_TYPE)) {
return resources.getStringArray(android.R.array.imProtocols);
} else if (mimetype.equals(Organization.CONTENT_ITEM_TYPE)) {
return resources.getStringArray(android.R.array.organizationTypes);
} else {
return resources.getStringArray(R.array.otherLabels);
}
}
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 BaseTypes.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, BaseTypes.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();
}
/**
* Gets the group id based on group name.
*
* @param resolver the resolver to use
* @param groupName the name of the group to add the contact to
* @return the id of the group
* @throws IllegalStateException if the group can't be found
*/
/*
private long getGroupId(ContentResolver resolver, String groupName) {
long groupId = 0;
Cursor groupsCursor = resolver.query(Groups.CONTENT_URI, GROUP_ID_PROJECTION,
Groups.NAME + "=?", new String[] { groupName }, null);
if (groupsCursor != null) {
try {
if (groupsCursor.moveToFirst()) {
groupId = groupsCursor.getLong(0);
}
} finally {
groupsCursor.close();
}
}
if (groupId == 0) {
throw new IllegalStateException("Failed to find the " + groupName + "group");
}
return groupId;
}
*/
/**
* Deletes group membership based on person and group ids.
*
* @param personId the person id
* @param groupId the group id
* @return the id of the group membership
*/
/*
private void deleteGroupMembership(long personId, long groupId) {
long groupMembershipId = 0;
Cursor groupsCursor = mResolver.query(GroupMembership.CONTENT_URI, GROUPMEMBERSHIP_ID_PROJECTION,
GroupMembership.PERSON_ID + "=? AND " + GroupMembership.GROUP_ID + "=?",
new String[] {String.valueOf(personId), String.valueOf(groupId)}, null);
if (groupsCursor != null) {
try {
if (groupsCursor.moveToFirst()) {
groupMembershipId = groupsCursor.getLong(0);
}
} finally {
groupsCursor.close();
}
}
if (groupMembershipId != 0) {
final Uri groupsUri = ContentUris.withAppendedId(
GroupMembership.CONTENT_URI,groupMembershipId);
mResolver.delete(groupsUri, null, null);
}
}
*/
/**
* 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(StructuredName.DISPLAY_NAME, name);
/*
values.put(People.PHONETIC_NAME, mPhoneticNameView.getText().toString());
*/
mResolver.update(mStructuredNameUri, values, null, null);
// This will go down in for loop somewhere
if (mPhotoChanged) {
// Only write the photo if it's changed, since we don't initially load mPhoto
values.clear();
if (mPhoto != null) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
mPhoto.compress(Bitmap.CompressFormat.JPEG, 75, stream);
values.put(Photo.PHOTO, stream.toByteArray());
mResolver.update(mPhotoDataUri, values, null, null);
} else {
values.putNull(Photo.PHOTO);
mResolver.update(mPhotoDataUri, values, null, null);
}
}
int entryCount = ContactEntryAdapter.countEntries(mSections, false);
for (int i = 0; i < entryCount; i++) {
EditEntry entry = ContactEntryAdapter.getEntry(mSections, i, false);
data = entry.getData();
boolean empty = data == null || !TextUtils.isGraphic(data);
/*
if (kind == EditEntry.KIND_GROUP) {
if (entry.id != 0) {
for (int g = 0; g < mGroups.length; g++) {
long groupId = getGroupId(mResolver, mGroups[g].toString());
if (mInTheGroup[g]) {
Contacts.People.addToGroup(mResolver, entry.id, groupId);
numValues++;
} else {
deleteGroupMembership(entry.id, groupId);
}
}
}
}
*/
if (!empty) {
values.clear();
entry.toValues(values);
if (entry.id != 0) {
mResolver.update(entry.uri, values, null, null);
} else {
/* mResolver.insert(entry.uri, values); */
}
} 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_GROUP) {
long contactId = ContentUris.parseId(contactUri);
for (int g = 0; g < mGroups.length; g++) {
if (mInTheGroup[g]) {
long groupId = getGroupId(mResolver, mGroups[g].toString());
People.addToGroup(mResolver, contactId, groupId);
numValues++;
}
}
} else 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);
}
} 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 (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 aggCursor = mResolver.query(mAggDataUri, AGGREGATE_PROJECTION, null, null, null);
if (aggCursor == null) {
Log.e(TAG, "invalid contact uri: " + mUri);
finish();
return;
} else if (!aggCursor.moveToFirst()) {
Log.e(TAG, "invalid contact uri: " + mUri);
finish();
aggCursor.close();
return;
}
// Clear out the old entries
int numSections = mSections.size();
for (int i = 0; i < numSections; i++) {
mSections.get(i).clear();
}
EditEntry entry;
while (aggCursor.moveToNext()) {
final String mimetype = aggCursor.getString(DATA_MIMETYPE_COLUMN);
boolean isSuperPrimary = aggCursor.getLong(DATA_IS_SUPER_PRIMARY_COLUMN) != 0;
final long id = aggCursor.getLong(DATA_ID_COLUMN);
final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
if (mimetype.equals(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)) {
mNameView.setText(aggCursor.getString(DATA_9_COLUMN));
mNameView.addTextChangedListener(this);
mStructuredNameUri = uri;
} else if (mimetype.equals(CommonDataKinds.Photo.CONTENT_ITEM_TYPE)) {
mPhoto = ContactsUtils.loadContactPhoto(aggCursor, DATA_1_COLUMN, null);
if (mPhoto == null) {
setPhotoPresent(false);
} else {
setPhotoPresent(true);
mPhotoImageView.setImageBitmap(mPhoto);
}
mPhotoDataUri = uri;
} else if (mimetype.equals(CommonDataKinds.Organization.CONTENT_ITEM_TYPE)) {
int type = aggCursor.getInt(DATA_1_COLUMN);
String label = aggCursor.getString(DATA_2_COLUMN);
String company = aggCursor.getString(DATA_3_COLUMN);
String title = aggCursor.getString(DATA_4_COLUMN);
entry = EditEntry.newOrganizationEntry(this, label, type, company, title, uri, id);
entry.isPrimary = aggCursor.getLong(DATA_IS_SUPER_PRIMARY_COLUMN) != 0;
mOrgEntries.add(entry);
} else if (mimetype.equals(CommonDataKinds.Note.CONTENT_ITEM_TYPE)) {
entry = EditEntry.newNotesEntry(this, aggCursor.getString(DATA_1_COLUMN),
uri, id);
mNoteEntries.add(entry);
} else if (mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
|| mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)
|| mimetype.equals(CommonDataKinds.Postal.CONTENT_ITEM_TYPE)
|| mimetype.equals(CommonDataKinds.Im.CONTENT_ITEM_TYPE)) {
int type = aggCursor.getInt(DATA_1_COLUMN);
String data = aggCursor.getString(DATA_2_COLUMN);
String label = aggCursor.getString(DATA_3_COLUMN);
if (mimetype.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) {
// Add a phone number entry
entry = EditEntry.newPhoneEntry(this, label, type, data, uri, id);
entry.isPrimary = isSuperPrimary;
mPhoneEntries.add(entry);
// Keep track of which primary types have been added
if (type == Phone.TYPE_MOBILE) {
mMobilePhoneAdded = true;
}
} else if (mimetype.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
entry = EditEntry.newEmailEntry(this, label, type, data, uri, id);
entry.isPrimary = isSuperPrimary;
mEmailEntries.add(entry);
if (isSuperPrimary) {
mPrimaryEmailAdded = true;
}
} else if (mimetype.equals(CommonDataKinds.Postal.CONTENT_ITEM_TYPE)) {
entry = EditEntry.newPostalEntry(this, label, type, data, uri, id);
entry.isPrimary = isSuperPrimary;
mPostalEntries.add(entry);
} else if (mimetype.equals(CommonDataKinds.Im.CONTENT_ITEM_TYPE)) {
String protocolStr = aggCursor.getString(DATA_5_COLUMN);
Object protocolObj = ContactsUtils.decodeImProtocol(protocolStr);
if (protocolObj == null) {
// Invalid IM protocol, log it then ignore.
Log.e(TAG, "Couldn't decode IM protocol: " + protocolStr);
continue;
} else {
if (protocolObj instanceof Number) {
int protocol = ((Number) protocolObj).intValue();
entry = EditEntry.newImEntry(this,
getLabelsForMimetype(this, mimetype)[protocol], protocol,
data, uri, id);
} else {
entry = EditEntry.newImEntry(this, protocolObj.toString(), -1, data,
uri, id);
}
mImEntries.add(entry);
}
}
}
}
/*
// Groups
populateGroups();
if (mGroups != null) {
entry = EditEntry.newGroupEntry(this, generateGroupList(), mUri,
personCursor.getLong(0));
mOtherEntries.add(entry);
}
// Phonetic name
mPhoneticNameView.setText(personCursor.getString(CONTACT_PHONETIC_NAME_COLUMN));
mPhoneticNameView.addTextChangedListener(this);
// 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, Phone.TYPE_MOBILE);
entry.isPrimary = true;
mPhoneEntries.add(entry);
}
if (!mPrimaryEmailAdded) {
entry = EditEntry.newEmailEntry(this, null, DEFAULT_EMAIL_TYPE);
entry.isPrimary = true;
mEmailEntries.add(entry);
}
// Group
populateGroups();
if (mGroups != null) {
entry = EditEntry.newGroupEntry(this, null, mUri, 0);
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 = ContactsContract.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 = Phone.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 == Phone.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.mimetype.equals(Organization.CONTENT_ITEM_TYPE)) {
view = mInflater.inflate(R.layout.edit_contact_entry_org, parent, false);
/*
else if (entry.mimetype.equals(Group.CONTENT_ITEM_TYPE)) {
view = mInflater.inflate(R.layout.edit_contact_entry_group, 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) {
/*
else if (isOtherEntry(entry, GroupMembership.GROUP_ID)) {
if (entry.data != null) {
updateDataView(entry, entry.data);
}
}
*/
}
/**
* 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.mimetype != Im.CONTENT_ITEM_TYPE) {
final int type = getTypeFromLabelPosition(mLabels, which);
if (type == BaseTypes.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;
}
v.setText(ContactsUtils.getDisplayLabel(context, mimetype, type, label));
if (mimetype.equals(Im.CONTENT_ITEM_TYPE) && type >= 0) {
v.setText(getLabelsForMimetype(activity, mimetype)[type]);
} else if (mimetype.equals(Postal.CONTENT_ITEM_TYPE)) {
v.setMaxLines(3);
}
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();
}
if (mimetype.equals(Phone.CONTENT_ITEM_TYPE)) {
if (type != Phone.TYPE_CUSTOM) {
labelString = null;
}
values.put(Phone.LABEL, labelString);
values.put(Phone.TYPE, type);
} else if (mimetype.equals(Email.CONTENT_ITEM_TYPE)) {
if (type != Email.TYPE_CUSTOM) {
labelString = null;
}
values.put(Email.LABEL, labelString);
values.put(Email.TYPE, type);
} else if (mimetype.equals(CommonDataKinds.Im.CONTENT_ITEM_TYPE)) {
values.put(CommonDataKinds.Im.TYPE, type);
values.putNull(CommonDataKinds.Im.LABEL);
if (type != -1) {
values.put(CommonDataKinds.Im.PROTOCOL,
ContactsUtils.encodePredefinedImProtocol(type));
} else {
values.put(CommonDataKinds.Im.PROTOCOL,
ContactsUtils.encodeCustomImProtocol(label.toString()));
}
} else if (mimetype.equals(CommonDataKinds.Postal.CONTENT_ITEM_TYPE)) {
if (type != Postal.TYPE_CUSTOM) {
labelString = null;
}
values.put(Postal.LABEL, labelString);
values.put(Postal.TYPE, type);
} else if (mimetype.equals(CommonDataKinds.Organization.CONTENT_ITEM_TYPE)) {
if (type != Organization.TYPE_CUSTOM) {
labelString = null;
}
values.put(Organization.LABEL, labelString);
values.put(Organization.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(Organization.TITLE);
} else {
values.put(Organization.TITLE, data2.toString());
success = true;
}
} else {
Log.i(TAG, "unknown entry mimetype: " + (mimetype == null ? "" : mimetype));
}
// 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(Data.IS_PRIMARY, 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 = Organization.COMPANY;
entry.mimetype = Organization.CONTENT_ITEM_TYPE;
entry.contentType = EditorInfo.TYPE_CLASS_TEXT
| EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
return entry;
}
/**
* Create a new empty notes entry
*/
public static final EditEntry newNotesEntry(EditContactActivity activity,
Uri uri) {
return newNotesEntry(activity, null, uri, 0);
}
/**
* Create a new notes entry with the given data.
*/
public static final EditEntry newNotesEntry(EditContactActivity activity,
String data, Uri uri, long id) {
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 = Note.NOTE;
entry.mimetype = Note.CONTENT_ITEM_TYPE;
entry.maxLines = 10;
entry.lines = 2;
entry.id = id;
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 group entry with the given data.
*/
/*
public static final EditEntry newGroupEntry(EditContactActivity activity,
String data, Uri uri, long personId) {
EditEntry entry = new EditEntry(activity);
entry.label = activity.getString(R.string.label_groups);
entry.data = data;
entry.uri = uri;
entry.id = personId;
entry.column = GroupMembership.GROUP_ID;
entry.kind = KIND_GROUP;
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 = Phone.NUMBER;
entry.mimetype = Phone.CONTENT_ITEM_TYPE;
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 = Email.DATA;
entry.mimetype = Email.CONTENT_ITEM_TYPE;
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 = Postal.DATA;
entry.mimetype = Postal.CONTENT_ITEM_TYPE;
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 = Im.DATA;
entry.mimetype = Im.CONTENT_ITEM_TYPE;
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);
}
}