Merge "Group editor: Handle rotation correctly"
diff --git a/src/com/android/contacts/activities/GroupEditorActivity.java b/src/com/android/contacts/activities/GroupEditorActivity.java
index f0d2aa1..80653d2 100644
--- a/src/com/android/contacts/activities/GroupEditorActivity.java
+++ b/src/com/android/contacts/activities/GroupEditorActivity.java
@@ -85,8 +85,13 @@
R.id.group_editor_fragment);
mFragment.setListener(mFragmentListener);
mFragment.setContentResolver(getContentResolver());
- Uri uri = Intent.ACTION_EDIT.equals(action) ? getIntent().getData() : null;
- mFragment.load(action, uri, getIntent().getExtras());
+
+ // NOTE The fragment will restore its state by itself after orientation changes, so
+ // we need to do this only for a new instance.
+ if (savedState == null) {
+ Uri uri = Intent.ACTION_EDIT.equals(action) ? getIntent().getData() : null;
+ mFragment.load(action, uri, getIntent().getExtras());
+ }
}
@Override
diff --git a/src/com/android/contacts/group/GroupEditorFragment.java b/src/com/android/contacts/group/GroupEditorFragment.java
index ccc84a2..f62a671 100644
--- a/src/com/android/contacts/group/GroupEditorFragment.java
+++ b/src/com/android/contacts/group/GroupEditorFragment.java
@@ -48,6 +48,8 @@
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;
@@ -63,7 +65,6 @@
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AutoCompleteTextView;
import android.widget.BaseAdapter;
-import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.QuickContactBadge;
@@ -73,13 +74,24 @@
import java.util.ArrayList;
import java.util.List;
-// TODO: Use savedInstanceState
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";
+
public static interface Listener {
/**
* Group metadata was not found, close the fragment now.
@@ -93,6 +105,8 @@
/**
* Title has been determined.
+ *
+ * TODO Remove this. No longer needed with the latest visual spec.
*/
void onTitleLoaded(int resourceId);
@@ -145,7 +159,8 @@
* Modes that specify the status of the editor
*/
public enum Status {
- LOADING, // Loader is fetching the data
+ 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
@@ -170,10 +185,11 @@
private TextView mAccountNameTextView;
private AutoCompleteTextView mAutoCompleteTextView;
- private boolean mGroupNameIsReadOnly;
private String mAccountName;
private String mAccountType;
private String mDataSet;
+
+ private boolean mGroupNameIsReadOnly;
private String mOriginalGroupName = "";
private MemberListAdapter mMemberListAdapter;
@@ -182,9 +198,9 @@
private ContentResolver mContentResolver;
private SuggestedMemberListAdapter mAutoCompleteAdapter;
- private List<Member> mListMembersToAdd = new ArrayList<Member>();
- private List<Member> mListMembersToRemove = new ArrayList<Member>();
- private List<Member> mListToDisplay = new ArrayList<Member>();
+ private final List<Member> mListMembersToAdd = new ArrayList<Member>();
+ private final List<Member> mListMembersToRemove = new ArrayList<Member>();
+ private final List<Member> mListToDisplay = new ArrayList<Member>();
public GroupEditorFragment() {
}
@@ -209,18 +225,19 @@
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- // Edit an existing group
- if (Intent.ACTION_EDIT.equals(mAction)) {
- if (mListener != null) {
- mListener.onTitleLoaded(R.string.editGroup_title_edit);
+ 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();
}
- getLoaderManager().initLoader(LOADER_GROUP_METADATA, null,
- mGroupMetaDataLoaderListener);
+ } else if (Intent.ACTION_EDIT.equals(mAction)) {
+ startGroupMetaDataLoader();
} else if (Intent.ACTION_INSERT.equals(mAction)) {
- if (mListener != null) {
- mListener.onTitleLoaded(R.string.editGroup_title_insert);
- }
-
final Account account = mIntentExtras == null ? null :
(Account) mIntentExtras.getParcelable(Intents.Insert.ACCOUNT);
final String dataSet = mIntentExtras == null ? null :
@@ -240,6 +257,58 @@
throw new IllegalArgumentException("Unknown Action String " + mAction +
". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT);
}
+
+ // Let the activity update the title.
+ if (mListener != null) {
+ mListener.onTitleLoaded(Intent.ACTION_EDIT.equals(mAction)
+ ? R.string.editGroup_title_edit
+ : R.string.editGroup_title_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.putParcelableArray(KEY_MEMBERS_TO_ADD, Member.toArray(mListMembersToAdd));
+ outState.putParcelableArray(KEY_MEMBERS_TO_REMOVE, Member.toArray(mListMembersToRemove));
+ outState.putParcelableArray(KEY_MEMBERS_TO_DISPLAY, Member.toArray(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);
+
+ Member.toList((Member[]) state.getParcelableArray(KEY_MEMBERS_TO_ADD), mListMembersToAdd);
+ Member.toList((Member[]) state.getParcelableArray(KEY_MEMBERS_TO_REMOVE),
+ mListMembersToRemove);
+ Member.toList((Member[]) state.getParcelableArray(KEY_MEMBERS_TO_DISPLAY), mListToDisplay);
}
public void setContentResolver(ContentResolver resolver) {
@@ -267,6 +336,7 @@
return; // Don't show a dialog.
}
+ mStatus = Status.SELECTING_ACCOUNT;
final SelectAccountDialogFragment dialog = new SelectAccountDialogFragment(
R.string.dialog_new_group_account);
dialog.setTargetFragment(this, 0);
@@ -345,8 +415,14 @@
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);
+
mRootView.addView(editorView);
mStatus = Status.EDITING;
}
@@ -359,30 +435,21 @@
}
private void bindGroupMetaData(Cursor cursor) {
- if (cursor.getCount() == 0) {
- if (mListener != null) {
- mListener.onGroupNotFound();
- }
- }
- try {
- cursor.moveToFirst();
- mOriginalGroupName = cursor.getString(GroupMetaDataLoader.TITLE);
- mAccountName = cursor.getString(GroupMetaDataLoader.ACCOUNT_NAME);
- mAccountType = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
- mGroupNameIsReadOnly = (cursor.getInt(GroupMetaDataLoader.IS_READ_ONLY) == 1);
- } catch (Exception e) {
+ if (!cursor.moveToFirst()) {
Log.i(TAG, "Group not found with URI: " + mGroupUri + " Closing activity now.");
if (mListener != null) {
mListener.onGroupNotFound();
}
- } finally {
- cursor.close();
+ return;
}
+ mOriginalGroupName = cursor.getString(GroupMetaDataLoader.TITLE);
+ mAccountName = cursor.getString(GroupMetaDataLoader.ACCOUNT_NAME);
+ mAccountType = cursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
+ mGroupNameIsReadOnly = (cursor.getInt(GroupMetaDataLoader.IS_READ_ONLY) == 1);
setupEditorForAccount();
- // Setup the group metadata display (If the group name is ready only, don't let the user
- // focus on the field).
+
+ // Setup the group metadata display
mGroupNameView.setText(mOriginalGroupName);
- mGroupNameView.setFocusable(!mGroupNameIsReadOnly);
}
public void loadMemberToAddToGroup(long rawContactId, String contactId) {
@@ -596,13 +663,13 @@
return membersArray;
}
- private void addExistingMembers(List<Member> members, List<Long> listContactIds) {
+ private void addExistingMembers(List<Member> members) {
mListToDisplay.addAll(members);
mMemberListAdapter.notifyDataSetChanged();
// Update the autocomplete adapter (if there is one) so these contacts don't get suggested
if (mAutoCompleteAdapter != null) {
- mAutoCompleteAdapter.updateExistingMembersList(listContactIds);
+ mAutoCompleteAdapter.updateExistingMembersList(members);
}
}
@@ -672,29 +739,23 @@
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- List<Long> listContactIds = new ArrayList<Long>();
List<Member> listExistingMembers = new ArrayList<Member>();
- try {
- data.moveToPosition(-1);
- while (data.moveToNext()) {
- long contactId = data.getLong(GroupMemberLoader.CONTACT_ID_COLUMN_INDEX);
- long rawContactId = data.getLong(GroupMemberLoader.RAW_CONTACT_ID_COLUMN_INDEX);
- String lookupKey = data.getString(
- GroupMemberLoader.CONTACT_LOOKUP_KEY_COLUMN_INDEX);
- String displayName = data.getString(
- GroupMemberLoader.CONTACT_DISPLAY_NAME_PRIMARY_COLUMN_INDEX);
- String photoUri = data.getString(
- GroupMemberLoader.CONTACT_PHOTO_URI_COLUMN_INDEX);
- listExistingMembers.add(new Member(rawContactId, lookupKey, contactId,
- displayName, photoUri));
- listContactIds.add(contactId);
- }
- } finally {
- data.close();
+ data.moveToPosition(-1);
+ while (data.moveToNext()) {
+ long contactId = data.getLong(GroupMemberLoader.CONTACT_ID_COLUMN_INDEX);
+ long rawContactId = data.getLong(GroupMemberLoader.RAW_CONTACT_ID_COLUMN_INDEX);
+ String lookupKey = data.getString(
+ GroupMemberLoader.CONTACT_LOOKUP_KEY_COLUMN_INDEX);
+ String displayName = data.getString(
+ GroupMemberLoader.CONTACT_DISPLAY_NAME_PRIMARY_COLUMN_INDEX);
+ String photoUri = data.getString(
+ GroupMemberLoader.CONTACT_PHOTO_URI_COLUMN_INDEX);
+ listExistingMembers.add(new Member(rawContactId, lookupKey, contactId,
+ displayName, photoUri));
}
// Update the display list
- addExistingMembers(listExistingMembers, listContactIds);
+ addExistingMembers(listExistingMembers);
// No more updates
// TODO: move to a runnable
@@ -725,26 +786,17 @@
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- // Retrieve the contact data fields that will be sufficient to update the adapter with
- // a new entry for this contact
- Member member = null;
- try {
- cursor.moveToFirst();
- 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 = new Member(mRawContactId, lookupKey, contactId, displayName, photoUri);
- } finally {
- cursor.close();
- }
-
- if (member == null) {
+ if (!cursor.moveToFirst()) {
return;
}
-
- // Otherwise continue adding the member to list of members
+ // 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);
}
@@ -755,7 +807,9 @@
/**
* This represents a single member of the current group.
*/
- public static class Member {
+ public static class Member implements Parcelable {
+ private static final Member[] EMPTY_ARRAY = new Member[0];
+
// TODO: Switch to just dealing with raw contact IDs everywhere if possible
private final long mRawContactId;
private final long mContactId;
@@ -800,6 +854,55 @@
}
return false;
}
+
+ // 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(mDisplayName);
+ dest.writeParcelable(mPhotoUri, flags);
+ }
+
+ private Member(Parcel in) {
+ mRawContactId = in.readLong();
+ mContactId = in.readLong();
+ mLookupUri = in.readParcelable(getClass().getClassLoader());
+ mDisplayName = in.readString();
+ mPhotoUri = in.readParcelable(getClass().getClassLoader());
+ }
+
+ public static final Parcelable.Creator<Member> CREATOR = new Parcelable.Creator<Member>() {
+ public Member createFromParcel(Parcel in) {
+ return new Member(in);
+ }
+
+ public Member[] newArray(int size) {
+ return new Member[size];
+ }
+ };
+
+ /** Convert to an array */
+ public static Member[] toArray(List<Member> list) {
+ return list.toArray(EMPTY_ARRAY);
+ }
+
+ /**
+ * Convert to a list. Instead of creating a new one, this method clears the passed list
+ * and adds elements to it.
+ */
+ public static void toList(Member[] array, List<Member> list) {
+ list.clear();
+ for (Member member : array) {
+ list.add(member);
+ }
+ }
}
/**
diff --git a/src/com/android/contacts/group/SuggestedMemberListAdapter.java b/src/com/android/contacts/group/SuggestedMemberListAdapter.java
index 653cc25..f671b65 100644
--- a/src/com/android/contacts/group/SuggestedMemberListAdapter.java
+++ b/src/com/android/contacts/group/SuggestedMemberListAdapter.java
@@ -82,7 +82,7 @@
// TODO: Make this a Map for better performance when we check if a new contact is in the list
// or not
- private List<Long> mExistingMemberContactIds = new ArrayList<Long>();
+ private final List<Long> mExistingMemberContactIds = new ArrayList<Long>();
private static final int SUGGESTIONS_LIMIT = 5;
@@ -107,8 +107,11 @@
mContentResolver = resolver;
}
- public void updateExistingMembersList(List<Long> listContactIds) {
- mExistingMemberContactIds = listContactIds;
+ public void updateExistingMembersList(List<GroupEditorFragment.Member> list) {
+ mExistingMemberContactIds.clear();
+ for (GroupEditorFragment.Member member : list) {
+ mExistingMemberContactIds.add(member.getContactId());
+ }
}
public void addNewMember(long contactId) {