blob: 3d13bdd942f9d6198b5197dd94852f110b677aa9 [file] [log] [blame]
/*
* Copyright (C) 2011 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.group;
import android.accounts.Account;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.CursorLoader;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AutoCompleteTextView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.QuickContactBadge;
import android.widget.TextView;
import android.widget.Toast;
import com.android.contacts.ContactSaveService;
import com.android.contacts.GroupMemberLoader;
import com.android.contacts.GroupMemberLoader.GroupEditorQuery;
import com.android.contacts.GroupMetaDataLoader;
import com.android.contacts.R;
import com.android.contacts.activities.GroupEditorActivity;
import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
import com.android.contacts.common.model.account.AccountType;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.editor.SelectAccountDialogFragment;
import com.android.contacts.group.SuggestedMemberListAdapter.SuggestedMember;
import com.android.contacts.common.model.AccountTypeManager;
import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter;
import com.android.contacts.common.util.ViewUtil;
import com.google.common.base.Objects;
import java.util.ArrayList;
import java.util.List;
public class GroupEditorFragment extends Fragment implements SelectAccountDialogFragment.Listener {
private static final String TAG = "GroupEditorFragment";
private static final String LEGACY_CONTACTS_AUTHORITY = "contacts";
private static final String KEY_ACTION = "action";
private static final String KEY_GROUP_URI = "groupUri";
private static final String KEY_GROUP_ID = "groupId";
private static final String KEY_STATUS = "status";
private static final String KEY_ACCOUNT_NAME = "accountName";
private static final String KEY_ACCOUNT_TYPE = "accountType";
private static final String KEY_DATA_SET = "dataSet";
private static final String KEY_GROUP_NAME_IS_READ_ONLY = "groupNameIsReadOnly";
private static final String KEY_ORIGINAL_GROUP_NAME = "originalGroupName";
private static final String KEY_MEMBERS_TO_ADD = "membersToAdd";
private static final String KEY_MEMBERS_TO_REMOVE = "membersToRemove";
private static final String KEY_MEMBERS_TO_DISPLAY = "membersToDisplay";
private static final String CURRENT_EDITOR_TAG = "currentEditorForAccount";
public static interface Listener {
/**
* Group metadata was not found, close the fragment now.
*/
public void onGroupNotFound();
/**
* User has tapped Revert, close the fragment now.
*/
void onReverted();
/**
* Contact was saved and the Fragment can now be closed safely.
*/
void onSaveFinished(int resultCode, Intent resultIntent);
/**
* Fragment is created but there's no accounts set up.
*/
void onAccountsNotFound();
}
private static final int LOADER_GROUP_METADATA = 1;
private static final int LOADER_EXISTING_MEMBERS = 2;
private static final int LOADER_NEW_GROUP_MEMBER = 3;
private static final String MEMBER_RAW_CONTACT_ID_KEY = "rawContactId";
private static final String MEMBER_LOOKUP_URI_KEY = "memberLookupUri";
protected static final String[] PROJECTION_CONTACT = new String[] {
Contacts._ID, // 0
Contacts.DISPLAY_NAME_PRIMARY, // 1
Contacts.DISPLAY_NAME_ALTERNATIVE, // 2
Contacts.SORT_KEY_PRIMARY, // 3
Contacts.STARRED, // 4
Contacts.CONTACT_PRESENCE, // 5
Contacts.CONTACT_CHAT_CAPABILITY, // 6
Contacts.PHOTO_ID, // 7
Contacts.PHOTO_THUMBNAIL_URI, // 8
Contacts.LOOKUP_KEY, // 9
Contacts.PHONETIC_NAME, // 10
Contacts.HAS_PHONE_NUMBER, // 11
Contacts.IS_USER_PROFILE, // 12
};
protected static final int CONTACT_ID_COLUMN_INDEX = 0;
protected static final int CONTACT_DISPLAY_NAME_PRIMARY_COLUMN_INDEX = 1;
protected static final int CONTACT_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX = 2;
protected static final int CONTACT_SORT_KEY_PRIMARY_COLUMN_INDEX = 3;
protected static final int CONTACT_STARRED_COLUMN_INDEX = 4;
protected static final int CONTACT_PRESENCE_STATUS_COLUMN_INDEX = 5;
protected static final int CONTACT_CHAT_CAPABILITY_COLUMN_INDEX = 6;
protected static final int CONTACT_PHOTO_ID_COLUMN_INDEX = 7;
protected static final int CONTACT_PHOTO_URI_COLUMN_INDEX = 8;
protected static final int CONTACT_LOOKUP_KEY_COLUMN_INDEX = 9;
protected static final int CONTACT_PHONETIC_NAME_COLUMN_INDEX = 10;
protected static final int CONTACT_HAS_PHONE_COLUMN_INDEX = 11;
protected static final int CONTACT_IS_USER_PROFILE = 12;
/**
* Modes that specify the status of the editor
*/
public enum Status {
SELECTING_ACCOUNT, // Account select dialog is showing
LOADING, // Loader is fetching the group metadata
EDITING, // Not currently busy. We are waiting forthe user to enter data.
SAVING, // Data is currently being saved
CLOSING // Prevents any more saves
}
private Context mContext;
private String mAction;
private Bundle mIntentExtras;
private Uri mGroupUri;
private long mGroupId;
private Listener mListener;
private Status mStatus;
private ViewGroup mRootView;
private ListView mListView;
private LayoutInflater mLayoutInflater;
private TextView mGroupNameView;
private AutoCompleteTextView mAutoCompleteTextView;
private String mAccountName;
private String mAccountType;
private String mDataSet;
private boolean mGroupNameIsReadOnly;
private String mOriginalGroupName = "";
private int mLastGroupEditorId;
private MemberListAdapter mMemberListAdapter;
private ContactPhotoManager mPhotoManager;
private ContentResolver mContentResolver;
private SuggestedMemberListAdapter mAutoCompleteAdapter;
private ArrayList<Member> mListMembersToAdd = new ArrayList<Member>();
private ArrayList<Member> mListMembersToRemove = new ArrayList<Member>();
private ArrayList<Member> mListToDisplay = new ArrayList<Member>();
public GroupEditorFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
setHasOptionsMenu(true);
mLayoutInflater = inflater;
mRootView = (ViewGroup) inflater.inflate(R.layout.group_editor_fragment, container, false);
return mRootView;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mContext = activity;
mPhotoManager = ContactPhotoManager.getInstance(mContext);
mMemberListAdapter = new MemberListAdapter();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
// Just restore from the saved state. No loading.
onRestoreInstanceState(savedInstanceState);
if (mStatus == Status.SELECTING_ACCOUNT) {
// Account select dialog is showing. Don't setup the editor yet.
} else if (mStatus == Status.LOADING) {
startGroupMetaDataLoader();
} else {
setupEditorForAccount();
}
} else if (Intent.ACTION_EDIT.equals(mAction)) {
startGroupMetaDataLoader();
} else if (Intent.ACTION_INSERT.equals(mAction)) {
final Account account = mIntentExtras == null ? null :
(Account) mIntentExtras.getParcelable(Intents.Insert.ACCOUNT);
final String dataSet = mIntentExtras == null ? null :
mIntentExtras.getString(Intents.Insert.DATA_SET);
if (account != null) {
// Account specified in Intent - no data set can be specified in this manner.
mAccountName = account.name;
mAccountType = account.type;
mDataSet = dataSet;
setupEditorForAccount();
} else {
// No Account specified. Let the user choose from a disambiguation dialog.
selectAccountAndCreateGroup();
}
} else {
throw new IllegalArgumentException("Unknown Action String " + mAction +
". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT);
}
}
private void startGroupMetaDataLoader() {
mStatus = Status.LOADING;
getLoaderManager().initLoader(LOADER_GROUP_METADATA, null,
mGroupMetaDataLoaderListener);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(KEY_ACTION, mAction);
outState.putParcelable(KEY_GROUP_URI, mGroupUri);
outState.putLong(KEY_GROUP_ID, mGroupId);
outState.putSerializable(KEY_STATUS, mStatus);
outState.putString(KEY_ACCOUNT_NAME, mAccountName);
outState.putString(KEY_ACCOUNT_TYPE, mAccountType);
outState.putString(KEY_DATA_SET, mDataSet);
outState.putBoolean(KEY_GROUP_NAME_IS_READ_ONLY, mGroupNameIsReadOnly);
outState.putString(KEY_ORIGINAL_GROUP_NAME, mOriginalGroupName);
outState.putParcelableArrayList(KEY_MEMBERS_TO_ADD, mListMembersToAdd);
outState.putParcelableArrayList(KEY_MEMBERS_TO_REMOVE, mListMembersToRemove);
outState.putParcelableArrayList(KEY_MEMBERS_TO_DISPLAY, mListToDisplay);
}
private void onRestoreInstanceState(Bundle state) {
mAction = state.getString(KEY_ACTION);
mGroupUri = state.getParcelable(KEY_GROUP_URI);
mGroupId = state.getLong(KEY_GROUP_ID);
mStatus = (Status) state.getSerializable(KEY_STATUS);
mAccountName = state.getString(KEY_ACCOUNT_NAME);
mAccountType = state.getString(KEY_ACCOUNT_TYPE);
mDataSet = state.getString(KEY_DATA_SET);
mGroupNameIsReadOnly = state.getBoolean(KEY_GROUP_NAME_IS_READ_ONLY);
mOriginalGroupName = state.getString(KEY_ORIGINAL_GROUP_NAME);
mListMembersToAdd = state.getParcelableArrayList(KEY_MEMBERS_TO_ADD);
mListMembersToRemove = state.getParcelableArrayList(KEY_MEMBERS_TO_REMOVE);
mListToDisplay = state.getParcelableArrayList(KEY_MEMBERS_TO_DISPLAY);
}
public void setContentResolver(ContentResolver resolver) {
mContentResolver = resolver;
if (mAutoCompleteAdapter != null) {
mAutoCompleteAdapter.setContentResolver(mContentResolver);
}
}
private void selectAccountAndCreateGroup() {
final List<AccountWithDataSet> accounts =
AccountTypeManager.getInstance(mContext).getAccounts(true /* writeable */);
// No Accounts available
if (accounts.isEmpty()) {
Log.e(TAG, "No accounts were found.");
if (mListener != null) {
mListener.onAccountsNotFound();
}
return;
}
// In the common case of a single account being writable, auto-select
// it without showing a dialog.
if (accounts.size() == 1) {
mAccountName = accounts.get(0).name;
mAccountType = accounts.get(0).type;
mDataSet = accounts.get(0).dataSet;
setupEditorForAccount();
return; // Don't show a dialog.
}
mStatus = Status.SELECTING_ACCOUNT;
SelectAccountDialogFragment.show(getFragmentManager(), this,
R.string.dialog_new_group_account, AccountListFilter.ACCOUNTS_GROUP_WRITABLE,
null);
}
@Override
public void onAccountChosen(AccountWithDataSet account, Bundle extraArgs) {
mAccountName = account.name;
mAccountType = account.type;
mDataSet = account.dataSet;
setupEditorForAccount();
}
@Override
public void onAccountSelectorCancelled() {
if (mListener != null) {
// Exit the fragment because we cannot continue without selecting an account
mListener.onGroupNotFound();
}
}
private AccountType getAccountType() {
return AccountTypeManager.getInstance(mContext).getAccountType(mAccountType, mDataSet);
}
/**
* @return true if the group membership is editable on this account type. false otherwise,
* or account is not set yet.
*/
private boolean isGroupMembershipEditable() {
if (mAccountType == null) {
return false;
}
return getAccountType().isGroupMembershipEditable();
}
/**
* Sets up the editor based on the group's account name and type.
*/
private void setupEditorForAccount() {
final AccountType accountType = getAccountType();
final boolean editable = isGroupMembershipEditable();
boolean isNewEditor = false;
mMemberListAdapter.setIsGroupMembershipEditable(editable);
// Since this method can be called multiple time, remove old editor if the editor type
// is different from the new one and mark the editor with a tag so it can be found for
// removal if needed
View editorView;
int newGroupEditorId =
editable ? R.layout.group_editor_view : R.layout.external_group_editor_view;
if (newGroupEditorId != mLastGroupEditorId) {
View oldEditorView = mRootView.findViewWithTag(CURRENT_EDITOR_TAG);
if (oldEditorView != null) {
mRootView.removeView(oldEditorView);
}
editorView = mLayoutInflater.inflate(newGroupEditorId, mRootView, false);
editorView.setTag(CURRENT_EDITOR_TAG);
mAutoCompleteAdapter = null;
mLastGroupEditorId = newGroupEditorId;
isNewEditor = true;
} else {
editorView = mRootView.findViewWithTag(CURRENT_EDITOR_TAG);
if (editorView == null) {
throw new IllegalStateException("Group editor view not found");
}
}
mGroupNameView = (TextView) editorView.findViewById(R.id.group_name);
mAutoCompleteTextView = (AutoCompleteTextView) editorView.findViewById(
R.id.add_member_field);
mListView = (ListView) editorView.findViewById(android.R.id.list);
mListView.setAdapter(mMemberListAdapter);
// Setup the account header, only when exists.
if (editorView.findViewById(R.id.account_header) != null) {
CharSequence accountTypeDisplayLabel = accountType.getDisplayLabel(mContext);
ImageView accountIcon = (ImageView) editorView.findViewById(R.id.account_icon);
TextView accountTypeTextView = (TextView) editorView.findViewById(R.id.account_type);
TextView accountNameTextView = (TextView) editorView.findViewById(R.id.account_name);
if (!TextUtils.isEmpty(mAccountName)) {
accountNameTextView.setText(
mContext.getString(R.string.from_account_format, mAccountName));
}
accountTypeTextView.setText(accountTypeDisplayLabel);
accountIcon.setImageDrawable(accountType.getDisplayIcon(mContext));
}
// Setup the autocomplete adapter (for contacts to suggest to add to the group) based on the
// account name and type. For groups that cannot have membership edited, there will be no
// autocomplete text view.
if (mAutoCompleteTextView != null) {
mAutoCompleteAdapter = new SuggestedMemberListAdapter(mContext,
android.R.layout.simple_dropdown_item_1line);
mAutoCompleteAdapter.setContentResolver(mContentResolver);
mAutoCompleteAdapter.setAccountType(mAccountType);
mAutoCompleteAdapter.setAccountName(mAccountName);
mAutoCompleteAdapter.setDataSet(mDataSet);
mAutoCompleteTextView.setAdapter(mAutoCompleteAdapter);
mAutoCompleteTextView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
SuggestedMember member = (SuggestedMember) view.getTag();
if (member == null) {
return; // just in case
}
loadMemberToAddToGroup(member.getRawContactId(),
String.valueOf(member.getContactId()));
// Update the autocomplete adapter so the contact doesn't get suggested again
mAutoCompleteAdapter.addNewMember(member.getContactId());
// Clear out the text field
mAutoCompleteTextView.setText("");
}
});
// Update the exempt list. (mListToDisplay might have been restored from the saved
// state.)
mAutoCompleteAdapter.updateExistingMembersList(mListToDisplay);
}
// If the group name is ready only, don't let the user focus on the field.
mGroupNameView.setFocusable(!mGroupNameIsReadOnly);
if(isNewEditor) {
mRootView.addView(editorView);
}
mStatus = Status.EDITING;
}
public void load(String action, Uri groupUri, Bundle intentExtras) {
mAction = action;
mGroupUri = groupUri;
mGroupId = (groupUri != null) ? ContentUris.parseId(mGroupUri) : 0;
mIntentExtras = intentExtras;
}
private void bindGroupMetaData(Cursor cursor) {
if (!cursor.moveToFirst()) {
Log.i(TAG, "Group not found with URI: " + mGroupUri + " Closing activity now.");
if (mListener != null) {
mListener.onGroupNotFound();
}
return;
}
mOriginalGroupName = cursor.getString(GroupMetaDataLoader.TITLE);
mAccountName = cursor.getString(GroupMetaDataLoader.ACCOUNT_NAME);
mAccountType = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
mDataSet = cursor.getString(GroupMetaDataLoader.DATA_SET);
mGroupNameIsReadOnly = (cursor.getInt(GroupMetaDataLoader.IS_READ_ONLY) == 1);
setupEditorForAccount();
// Setup the group metadata display
mGroupNameView.setText(mOriginalGroupName);
}
public void loadMemberToAddToGroup(long rawContactId, String contactId) {
Bundle args = new Bundle();
args.putLong(MEMBER_RAW_CONTACT_ID_KEY, rawContactId);
args.putString(MEMBER_LOOKUP_URI_KEY, contactId);
getLoaderManager().restartLoader(LOADER_NEW_GROUP_MEMBER, args, mContactLoaderListener);
}
public void setListener(Listener value) {
mListener = value;
}
public void onDoneClicked() {
if (isGroupMembershipEditable()) {
save();
} else {
// Just revert it.
doRevertAction();
}
}
@Override
public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.edit_group, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_discard:
return revert();
}
return false;
}
private boolean revert() {
if (!hasNameChange() && !hasMembershipChange()) {
doRevertAction();
} else {
CancelEditDialogFragment.show(this);
}
return true;
}
private void doRevertAction() {
// When this Fragment is closed we don't want it to auto-save
mStatus = Status.CLOSING;
if (mListener != null) mListener.onReverted();
}
public static class CancelEditDialogFragment extends DialogFragment {
public static void show(GroupEditorFragment fragment) {
CancelEditDialogFragment dialog = new CancelEditDialogFragment();
dialog.setTargetFragment(fragment, 0);
dialog.show(fragment.getFragmentManager(), "cancelEditor");
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog dialog = new AlertDialog.Builder(getActivity())
.setIconAttribute(android.R.attr.alertDialogIcon)
.setMessage(R.string.cancel_confirmation_dialog_message)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int whichButton) {
((GroupEditorFragment) getTargetFragment()).doRevertAction();
}
}
)
.setNegativeButton(android.R.string.cancel, null)
.create();
return dialog;
}
}
/**
* Saves or creates the group based on the mode, and if successful
* finishes the activity. This actually only handles saving the group name.
* @return true when successful
*/
public boolean save() {
if (!hasValidGroupName() || mStatus != Status.EDITING) {
mStatus = Status.CLOSING;
if (mListener != null) {
mListener.onReverted();
}
return false;
}
// If we are about to close the editor - there is no need to refresh the data
getLoaderManager().destroyLoader(LOADER_EXISTING_MEMBERS);
// If there are no changes, then go straight to onSaveCompleted()
if (!hasNameChange() && !hasMembershipChange()) {
onSaveCompleted(false, mGroupUri);
return true;
}
mStatus = Status.SAVING;
Activity activity = getActivity();
// If the activity is not there anymore, then we can't continue with the save process.
if (activity == null) {
return false;
}
Intent saveIntent = null;
if (Intent.ACTION_INSERT.equals(mAction)) {
// Create array of raw contact IDs for contacts to add to the group
long[] membersToAddArray = convertToArray(mListMembersToAdd);
// Create the save intent to create the group and add members at the same time
saveIntent = ContactSaveService.createNewGroupIntent(activity,
new AccountWithDataSet(mAccountName, mAccountType, mDataSet),
mGroupNameView.getText().toString(),
membersToAddArray, activity.getClass(),
GroupEditorActivity.ACTION_SAVE_COMPLETED);
} else if (Intent.ACTION_EDIT.equals(mAction)) {
// Create array of raw contact IDs for contacts to add to the group
long[] membersToAddArray = convertToArray(mListMembersToAdd);
// Create array of raw contact IDs for contacts to add to the group
long[] membersToRemoveArray = convertToArray(mListMembersToRemove);
// Create the update intent (which includes the updated group name if necessary)
saveIntent = ContactSaveService.createGroupUpdateIntent(activity, mGroupId,
getUpdatedName(), membersToAddArray, membersToRemoveArray,
activity.getClass(), GroupEditorActivity.ACTION_SAVE_COMPLETED);
} else {
throw new IllegalStateException("Invalid intent action type " + mAction);
}
activity.startService(saveIntent);
return true;
}
public void onSaveCompleted(boolean hadChanges, Uri groupUri) {
boolean success = groupUri != null;
Log.d(TAG, "onSaveCompleted(" + groupUri + ")");
if (hadChanges) {
Toast.makeText(mContext, success ? R.string.groupSavedToast :
R.string.groupSavedErrorToast, Toast.LENGTH_SHORT).show();
}
final Intent resultIntent;
final int resultCode;
if (success && groupUri != null) {
final String requestAuthority = groupUri.getAuthority();
resultIntent = new Intent();
if (LEGACY_CONTACTS_AUTHORITY.equals(requestAuthority)) {
// Build legacy Uri when requested by caller
final long groupId = ContentUris.parseId(groupUri);
final Uri legacyContentUri = Uri.parse("content://contacts/groups");
final Uri legacyUri = ContentUris.withAppendedId(
legacyContentUri, groupId);
resultIntent.setData(legacyUri);
} else {
// Otherwise pass back the given Uri
resultIntent.setData(groupUri);
}
resultCode = Activity.RESULT_OK;
} else {
resultCode = Activity.RESULT_CANCELED;
resultIntent = null;
}
// It is already saved, so prevent that it is saved again
mStatus = Status.CLOSING;
if (mListener != null) {
mListener.onSaveFinished(resultCode, resultIntent);
}
}
private boolean hasValidGroupName() {
return mGroupNameView != null && !TextUtils.isEmpty(mGroupNameView.getText());
}
private boolean hasNameChange() {
return mGroupNameView != null &&
!mGroupNameView.getText().toString().equals(mOriginalGroupName);
}
private boolean hasMembershipChange() {
return mListMembersToAdd.size() > 0 || mListMembersToRemove.size() > 0;
}
/**
* Returns the group's new name or null if there is no change from the
* original name that was loaded for the group.
*/
private String getUpdatedName() {
String groupNameFromTextView = mGroupNameView.getText().toString();
if (groupNameFromTextView.equals(mOriginalGroupName)) {
// No name change, so return null
return null;
}
return groupNameFromTextView;
}
private static long[] convertToArray(List<Member> listMembers) {
int size = listMembers.size();
long[] membersArray = new long[size];
for (int i = 0; i < size; i++) {
membersArray[i] = listMembers.get(i).getRawContactId();
}
return membersArray;
}
private void addExistingMembers(List<Member> members) {
// Re-create the list to display
mListToDisplay.clear();
mListToDisplay.addAll(members);
mListToDisplay.addAll(mListMembersToAdd);
mListToDisplay.removeAll(mListMembersToRemove);
mMemberListAdapter.notifyDataSetChanged();
// Update the autocomplete adapter (if there is one) so these contacts don't get suggested
if (mAutoCompleteAdapter != null) {
mAutoCompleteAdapter.updateExistingMembersList(members);
}
}
private void addMember(Member member) {
// Update the display list
mListMembersToAdd.add(member);
mListToDisplay.add(member);
mMemberListAdapter.notifyDataSetChanged();
// Update the autocomplete adapter so the contact doesn't get suggested again
mAutoCompleteAdapter.addNewMember(member.getContactId());
}
private void removeMember(Member member) {
// If the contact was just added during this session, remove it from the list of
// members to add
if (mListMembersToAdd.contains(member)) {
mListMembersToAdd.remove(member);
} else {
// Otherwise this contact was already part of the existing list of contacts,
// so we need to do a content provider deletion operation
mListMembersToRemove.add(member);
}
// In either case, update the UI so the contact is no longer in the list of
// members
mListToDisplay.remove(member);
mMemberListAdapter.notifyDataSetChanged();
// Update the autocomplete adapter so the contact can get suggested again
mAutoCompleteAdapter.removeMember(member.getContactId());
}
/**
* The listener for the group metadata (i.e. group name, account type, and account name) loader.
*/
private final LoaderManager.LoaderCallbacks<Cursor> mGroupMetaDataLoaderListener =
new LoaderCallbacks<Cursor>() {
@Override
public CursorLoader onCreateLoader(int id, Bundle args) {
return new GroupMetaDataLoader(mContext, mGroupUri);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
bindGroupMetaData(data);
// Load existing members
getLoaderManager().initLoader(LOADER_EXISTING_MEMBERS, null,
mGroupMemberListLoaderListener);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {}
};
/**
* The loader listener for the list of existing group members.
*/
private final LoaderManager.LoaderCallbacks<Cursor> mGroupMemberListLoaderListener =
new LoaderCallbacks<Cursor>() {
@Override
public CursorLoader onCreateLoader(int id, Bundle args) {
return GroupMemberLoader.constructLoaderForGroupEditorQuery(mContext, mGroupId);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
List<Member> listExistingMembers = new ArrayList<Member>();
data.moveToPosition(-1);
while (data.moveToNext()) {
long contactId = data.getLong(GroupEditorQuery.CONTACT_ID);
long rawContactId = data.getLong(GroupEditorQuery.RAW_CONTACT_ID);
String lookupKey = data.getString(GroupEditorQuery.CONTACT_LOOKUP_KEY);
String displayName = data.getString(GroupEditorQuery.CONTACT_DISPLAY_NAME_PRIMARY);
String photoUri = data.getString(GroupEditorQuery.CONTACT_PHOTO_URI);
listExistingMembers.add(new Member(rawContactId, lookupKey, contactId,
displayName, photoUri));
}
// Update the display list
addExistingMembers(listExistingMembers);
// No more updates
// TODO: move to a runnable
getLoaderManager().destroyLoader(LOADER_EXISTING_MEMBERS);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {}
};
/**
* The listener to load a summary of details for a contact.
*/
// TODO: Remove this step because showing the aggregate contact can be confusing when the user
// just selected a raw contact
private final LoaderManager.LoaderCallbacks<Cursor> mContactLoaderListener =
new LoaderCallbacks<Cursor>() {
private long mRawContactId;
@Override
public CursorLoader onCreateLoader(int id, Bundle args) {
String memberId = args.getString(MEMBER_LOOKUP_URI_KEY);
mRawContactId = args.getLong(MEMBER_RAW_CONTACT_ID_KEY);
return new CursorLoader(mContext, Uri.withAppendedPath(Contacts.CONTENT_URI, memberId),
PROJECTION_CONTACT, null, null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if (!cursor.moveToFirst()) {
return;
}
// Retrieve the contact data fields that will be sufficient to update the adapter with
// a new entry for this contact
long contactId = cursor.getLong(CONTACT_ID_COLUMN_INDEX);
String displayName = cursor.getString(CONTACT_DISPLAY_NAME_PRIMARY_COLUMN_INDEX);
String lookupKey = cursor.getString(CONTACT_LOOKUP_KEY_COLUMN_INDEX);
String photoUri = cursor.getString(CONTACT_PHOTO_URI_COLUMN_INDEX);
getLoaderManager().destroyLoader(LOADER_NEW_GROUP_MEMBER);
Member member = new Member(mRawContactId, lookupKey, contactId, displayName, photoUri);
addMember(member);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {}
};
/**
* This represents a single member of the current group.
*/
public static class Member implements Parcelable {
// TODO: Switch to just dealing with raw contact IDs everywhere if possible
private final long mRawContactId;
private final long mContactId;
private final Uri mLookupUri;
private final String mDisplayName;
private final Uri mPhotoUri;
private final String mLookupKey;
public Member(long rawContactId, String lookupKey, long contactId, String displayName,
String photoUri) {
mRawContactId = rawContactId;
mContactId = contactId;
mLookupKey = lookupKey;
mLookupUri = Contacts.getLookupUri(contactId, lookupKey);
mDisplayName = displayName;
mPhotoUri = (photoUri != null) ? Uri.parse(photoUri) : null;
}
public long getRawContactId() {
return mRawContactId;
}
public long getContactId() {
return mContactId;
}
public Uri getLookupUri() {
return mLookupUri;
}
public String getLookupKey() {
return mLookupKey;
}
public String getDisplayName() {
return mDisplayName;
}
public Uri getPhotoUri() {
return mPhotoUri;
}
@Override
public boolean equals(Object object) {
if (object instanceof Member) {
Member otherMember = (Member) object;
return Objects.equal(mLookupUri, otherMember.getLookupUri());
}
return false;
}
@Override
public int hashCode() {
return mLookupUri == null ? 0 : mLookupUri.hashCode();
}
// Parcelable
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(mRawContactId);
dest.writeLong(mContactId);
dest.writeParcelable(mLookupUri, flags);
dest.writeString(mLookupKey);
dest.writeString(mDisplayName);
dest.writeParcelable(mPhotoUri, flags);
}
private Member(Parcel in) {
mRawContactId = in.readLong();
mContactId = in.readLong();
mLookupUri = in.readParcelable(getClass().getClassLoader());
mLookupKey = in.readString();
mDisplayName = in.readString();
mPhotoUri = in.readParcelable(getClass().getClassLoader());
}
public static final Parcelable.Creator<Member> CREATOR = new Parcelable.Creator<Member>() {
@Override
public Member createFromParcel(Parcel in) {
return new Member(in);
}
@Override
public Member[] newArray(int size) {
return new Member[size];
}
};
}
/**
* This adapter displays a list of members for the current group being edited.
*/
private final class MemberListAdapter extends BaseAdapter {
private boolean mIsGroupMembershipEditable = true;
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View result;
if (convertView == null) {
result = mLayoutInflater.inflate(mIsGroupMembershipEditable ?
R.layout.group_member_item : R.layout.external_group_member_item,
parent, false);
} else {
result = convertView;
}
final Member member = getItem(position);
QuickContactBadge badge = (QuickContactBadge) result.findViewById(R.id.badge);
badge.assignContactUri(member.getLookupUri());
TextView name = (TextView) result.findViewById(R.id.name);
name.setText(member.getDisplayName());
View deleteButton = result.findViewById(R.id.delete_button_container);
if (deleteButton != null) {
deleteButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
removeMember(member);
}
});
}
DefaultImageRequest request = new DefaultImageRequest(member.getDisplayName(),
member.getLookupKey(), true /* isCircular */);
mPhotoManager.loadPhoto(badge, member.getPhotoUri(),
ViewUtil.getConstantPreLayoutWidth(badge), false, true /* isCircular */,
request);
return result;
}
@Override
public int getCount() {
return mListToDisplay.size();
}
@Override
public Member getItem(int position) {
return mListToDisplay.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
public void setIsGroupMembershipEditable(boolean editable) {
mIsGroupMembershipEditable = editable;
}
}
}