diff options
7 files changed, 146 insertions, 70 deletions
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java index 916ae7096..da0d91e6b 100644 --- a/src/com/android/contacts/editor/ContactEditorFragment.java +++ b/src/com/android/contacts/editor/ContactEditorFragment.java @@ -67,26 +67,27 @@ import com.android.contacts.R; import com.android.contacts.activities.ContactEditorAccountsChangedActivity; import com.android.contacts.activities.ContactEditorActivity; import com.android.contacts.activities.JoinContactActivity; +import com.android.contacts.common.model.AccountTypeManager; +import com.android.contacts.common.model.ValuesDelta; +import com.android.contacts.common.model.account.AccountType; +import com.android.contacts.common.model.account.AccountWithDataSet; +import com.android.contacts.common.model.account.GoogleAccountType; +import com.android.contacts.common.util.AccountsListAdapter; +import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter; import com.android.contacts.detail.PhotoSelectionHandler; import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion; import com.android.contacts.editor.Editor.EditorListener; -import com.android.contacts.common.model.AccountTypeManager; import com.android.contacts.model.Contact; import com.android.contacts.model.ContactLoader; import com.android.contacts.model.RawContact; import com.android.contacts.model.RawContactDelta; -import com.android.contacts.common.model.ValuesDelta; import com.android.contacts.model.RawContactDeltaList; import com.android.contacts.model.RawContactModifier; -import com.android.contacts.common.model.account.AccountType; -import com.android.contacts.common.model.account.AccountWithDataSet; -import com.android.contacts.common.model.account.GoogleAccountType; -import com.android.contacts.common.util.AccountsListAdapter; -import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter; import com.android.contacts.util.ContactPhotoUtils; import com.android.contacts.util.HelpUtils; import com.android.contacts.util.UiClosables; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import java.io.File; import java.util.ArrayList; @@ -118,6 +119,11 @@ public class ContactEditorFragment extends Fragment implements private static final String KEY_NEW_LOCAL_PROFILE = "newLocalProfile"; private static final String KEY_IS_USER_PROFILE = "isUserProfile"; private static final String KEY_UPDATED_PHOTOS = "updatedPhotos"; + private static final String KEY_IS_EDIT = "isEdit"; + private static final String KEY_HAS_NEW_CONTACT = "hasNewContact"; + private static final String KEY_NEW_CONTACT_READY = "newContactDataReady"; + private static final String KEY_EXISTING_CONTACT_READY = "existingContactDataReady"; + private static final String KEY_RAW_CONTACTS = "rawContacts"; public static final String SAVE_MODE_EXTRA_KEY = "saveMode"; @@ -236,6 +242,21 @@ public class ContactEditorFragment extends Fragment implements private int mStatus; + // Whether to show the new contact blank form and if it's corresponding delta is ready. + private boolean mHasNewContact = false; + private boolean mNewContactDataReady = false; + + // Whether it's an edit of existing contact and if it's corresponding delta is ready. + private boolean mIsEdit = false; + private boolean mExistingContactDataReady = false; + + // This is used to pre-populate the editor with a display name when a user edits a read-only + // contact. + private String mDefaultDisplayName; + + // Used to temporarily store existing contact data during a rebind call (i.e. account switch) + private ImmutableList<RawContact> mRawContacts; + private AggregationSuggestionEngine mAggregationSuggestionEngine; private long mAggregationSuggestionsRawContactId; private View mAggregationSuggestionView; @@ -365,10 +386,7 @@ public class ContactEditorFragment extends Fragment implements validateAction(mAction); - // Handle initial actions only when existing state missing - final boolean hasIncomingState = savedInstanceState != null; - - if (mState == null) { + if (mState.isEmpty()) { // The delta list may not have finished loading before orientation change happens. // In this case, there will be a saved state but deltas will be missing. Reload from // database. @@ -384,8 +402,12 @@ public class ContactEditorFragment extends Fragment implements bindEditors(); } - if (!hasIncomingState) { - if (Intent.ACTION_INSERT.equals(mAction)) { + // Handle initial actions only when existing state missing + if (savedInstanceState == null) { + if (Intent.ACTION_EDIT.equals(mAction)) { + mIsEdit = true; + } else if (Intent.ACTION_INSERT.equals(mAction)) { + mHasNewContact = true; final Account account = mIntentExtras == null ? null : (Account) mIntentExtras.getParcelable(Intents.Insert.ACCOUNT); final String dataSet = mIntentExtras == null ? null : @@ -468,20 +490,34 @@ public class ContactEditorFragment extends Fragment implements mNewLocalProfile = savedState.getBoolean(KEY_NEW_LOCAL_PROFILE); mIsUserProfile = savedState.getBoolean(KEY_IS_USER_PROFILE); mUpdatedPhotos = savedState.getParcelable(KEY_UPDATED_PHOTOS); + mIsEdit = savedState.getBoolean(KEY_IS_EDIT); + mHasNewContact = savedState.getBoolean(KEY_HAS_NEW_CONTACT); + mNewContactDataReady = savedState.getBoolean(KEY_NEW_CONTACT_READY); + mExistingContactDataReady = savedState.getBoolean(KEY_EXISTING_CONTACT_READY); + mRawContacts = ImmutableList.copyOf(savedState.<RawContact>getParcelableArrayList( + KEY_RAW_CONTACTS)); + + } + + // mState can still be null because it may not have have finished loading before + // onSaveInstanceState was called. + if (mState == null) { + mState = new RawContactDeltaList(); } } - public void setData(Contact data) { + public void setData(Contact contact) { + // If we have already loaded data, we do not want to change it here to not confuse the user - if (mState != null) { + if (!mState.isEmpty()) { Log.v(TAG, "Ignoring background change. This will have to be rebased later"); return; } // See if this edit operation needs to be redirected to a custom editor - ImmutableList<RawContact> rawContacts = data.getRawContacts(); - if (rawContacts.size() == 1) { - RawContact rawContact = rawContacts.get(0); + mRawContacts = contact.getRawContacts(); + if (mRawContacts.size() == 1) { + RawContact rawContact = mRawContacts.get(0); String type = rawContact.getAccountTypeString(); String dataSet = rawContact.getDataSet(); AccountType accountType = rawContact.getAccountType(mContext); @@ -499,7 +535,18 @@ public class ContactEditorFragment extends Fragment implements } } - bindEditorsForExistingContact(data); + // Check for writable raw contacts. If there are none, then we need to create one so user + // can edit. For the user profile case, there is already an editable contact. + if (!contact.isUserProfile() && !contact.isWritableContact(mContext)) { + mHasNewContact = true; + + // This is potentially an asynchronous call and will add deltas to list. + selectAccountAndCreateContact(); + } + + // This also adds deltas to list + bindEditorsForExistingContact(contact.getDisplayName(), contact.isUserProfile(), + mRawContacts); } @Override @@ -507,15 +554,17 @@ public class ContactEditorFragment extends Fragment implements mListener.onCustomEditContactActivityRequested(account, uri, null, false); } - private void bindEditorsForExistingContact(Contact contact) { + private void bindEditorsForExistingContact(String displayName, boolean isUserProfile, + ImmutableList<RawContact> rawContacts) { setEnabled(true); + mDefaultDisplayName = displayName; - mState = contact.createRawContactDeltaList(); + mState.addAll(rawContacts.iterator()); setIntentExtras(mIntentExtras); mIntentExtras = null; // For user profile, change the contacts query URI - mIsUserProfile = contact.isUserProfile(); + mIsUserProfile = isUserProfile; boolean localProfileExists = false; if (mIsUserProfile) { @@ -539,7 +588,7 @@ public class ContactEditorFragment extends Fragment implements } } mRequestFocus = true; - + mExistingContactDataReady = true; bindEditors(); } @@ -649,8 +698,13 @@ public class ContactEditorFragment extends Fragment implements mListener.onCustomCreateContactActivityRequested(newAccount, mIntentExtras); } } else { - mState = null; + mExistingContactDataReady = false; + mNewContactDataReady = false; + mState = new RawContactDeltaList(); bindEditorsForNewContact(newAccount, newAccountType, oldState, oldAccountType); + if (mIsEdit) { + bindEditorsForExistingContact(mDefaultDisplayName, mIsUserProfile, mRawContacts); + } } } @@ -671,7 +725,8 @@ public class ContactEditorFragment extends Fragment implements rawContact.setAccountToLocal(); } - RawContactDelta insert = new RawContactDelta(ValuesDelta.fromAfter(rawContact.getValues())); + final ValuesDelta valuesDelta = ValuesDelta.fromAfter(rawContact.getValues()); + final RawContactDelta insert = new RawContactDelta(valuesDelta); if (oldState == null) { // Parse any values from incoming intent RawContactModifier.parseExtras(mContext, newAccountType, insert, mIntentExtras); @@ -694,23 +749,25 @@ public class ContactEditorFragment extends Fragment implements insert.setProfileQueryUri(); } - if (mState == null) { - // Create state if none exists yet - mState = RawContactDeltaList.fromSingle(insert); - } else { - // Add contact onto end of existing state - mState.add(insert); - } + mState.add(insert); mRequestFocus = true; + mNewContactDataReady = true; bindEditors(); } private void bindEditors() { // bindEditors() can only bind views if there is data in mState, so immediately return // if mState is null - if (mState == null) { + if (mState.isEmpty()) { + return; + } + + // Check if delta list is ready. Delta list is populated from existing data and when + // editing an read-only contact, it's also populated with newly created data for the + // blank form. When the data is not ready, skip. This method will be called multiple times. + if ((mIsEdit && !mExistingContactDataReady) || (mHasNewContact && !mNewContactDataReady)) { return; } @@ -724,6 +781,7 @@ public class ContactEditorFragment extends Fragment implements Context.LAYOUT_INFLATER_SERVICE); final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext); int numRawContacts = mState.size(); + for (int i = 0; i < numRawContacts; i++) { // TODO ensure proper ordering of entities in the list final RawContactDelta rawContactDelta = mState.get(i); @@ -741,10 +799,10 @@ public class ContactEditorFragment extends Fragment implements editor = (RawContactEditorView) inflater.inflate(R.layout.raw_contact_editor_view, mContent, false); } - if (Intent.ACTION_INSERT.equals(mAction) && numRawContacts == 1) { + if (mHasNewContact && !mNewLocalProfile) { final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(mContext).getAccounts(true); - if (accounts.size() > 1 && !mNewLocalProfile) { + if (accounts.size() > 1) { addAccountSwitcher(mState.get(0), editor); } else { disableAccountSwitcher(editor); @@ -787,12 +845,13 @@ public class ContactEditorFragment extends Fragment implements } }; - final TextFieldsEditorView nameEditor = rawContactEditor.getNameEditor(); + final StructuredNameEditorView nameEditor = rawContactEditor.getNameEditor(); if (mRequestFocus) { nameEditor.requestFocus(); mRequestFocus = false; } nameEditor.setEditorListener(listener); + nameEditor.setDisplayName(mDefaultDisplayName); final TextFieldsEditorView phoneticNameEditor = rawContactEditor.getPhoneticNameEditor(); @@ -959,7 +1018,7 @@ public class ContactEditorFragment extends Fragment implements doneMenu.setVisible(false); // Split only if more than one raw profile and not a user profile - splitMenu.setVisible(mState != null && mState.size() > 1 && !isEditingUserProfile()); + splitMenu.setVisible(mState.size() > 1 && !isEditingUserProfile()); // Cannot join a user profile joinMenu.setVisible(!isEditingUserProfile()); @@ -1032,7 +1091,7 @@ public class ContactEditorFragment extends Fragment implements * performing user actions. */ private boolean hasValidState() { - return mState != null && mState.size() > 0; + return mState.size() > 0; } /** @@ -1117,7 +1176,7 @@ public class ContactEditorFragment extends Fragment implements } private boolean revert() { - if (mState == null || !hasPendingChanges()) { + if (mState.isEmpty() || !hasPendingChanges()) { doRevertAction(); } else { CancelEditDialogFragment.show(this); @@ -1193,7 +1252,7 @@ public class ContactEditorFragment extends Fragment implements // If this was in INSERT, we are changing into an EDIT now. // If it already was an EDIT, we are changing to the new Uri now - mState = null; + mState = new RawContactDeltaList(); load(Intent.ACTION_EDIT, contactLookupUri, null); mStatus = Status.LOADING; getLoaderManager().restartLoader(LOADER_DATA, null, mDataLoaderListener); @@ -1393,12 +1452,10 @@ public class ContactEditorFragment extends Fragment implements * Returns the contact ID for the currently edited contact or 0 if the contact is new. */ protected long getContactId() { - if (mState != null) { - for (RawContactDelta rawContact : mState) { - Long contactId = rawContact.getValues().getAsLong(RawContacts.CONTACT_ID); - if (contactId != null) { - return contactId; - } + for (RawContactDelta rawContact : mState) { + Long contactId = rawContact.getValues().getAsLong(RawContacts.CONTACT_ID); + if (contactId != null) { + return contactId; } } return 0; @@ -1435,7 +1492,7 @@ public class ContactEditorFragment extends Fragment implements public void onAggregationSuggestionChange() { Activity activity = getActivity(); if ((activity != null && activity.isFinishing()) - || !isVisible() || mState == null || mStatus != Status.EDITING) { + || !isVisible() || mState.isEmpty() || mStatus != Status.EDITING) { return; } @@ -1600,6 +1657,11 @@ public class ContactEditorFragment extends Fragment implements outState.putBoolean(KEY_IS_USER_PROFILE, mIsUserProfile); outState.putInt(KEY_STATUS, mStatus); outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos); + outState.putBoolean(KEY_HAS_NEW_CONTACT, mHasNewContact); + outState.putBoolean(KEY_IS_EDIT, mIsEdit); + outState.putBoolean(KEY_NEW_CONTACT_READY, mNewContactDataReady); + outState.putBoolean(KEY_EXISTING_CONTACT_READY, mExistingContactDataReady); + outState.putParcelableArrayList(KEY_RAW_CONTACTS, Lists.newArrayList(mRawContacts)); super.onSaveInstanceState(outState); } @@ -1778,7 +1840,7 @@ public class ContactEditorFragment extends Fragment implements @Override public void onSplitContactConfirmed() { - if (mState == null) { + if (mState.isEmpty()) { // This may happen when this Fragment is recreated by the system during users // confirming the split action (and thus this method is called just before onCreate()), // for example. diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java index d069c8d53..909930788 100644 --- a/src/com/android/contacts/editor/RawContactEditorView.java +++ b/src/com/android/contacts/editor/RawContactEditorView.java @@ -418,7 +418,7 @@ public class RawContactEditorView extends BaseRawContactEditorView { return -1; } - public TextFieldsEditorView getNameEditor() { + public StructuredNameEditorView getNameEditor() { return mName; } diff --git a/src/com/android/contacts/editor/StructuredNameEditorView.java b/src/com/android/contacts/editor/StructuredNameEditorView.java index f7090212c..4d7259857 100644 --- a/src/com/android/contacts/editor/StructuredNameEditorView.java +++ b/src/com/android/contacts/editor/StructuredNameEditorView.java @@ -200,6 +200,18 @@ public class StructuredNameEditorView extends TextFieldsEditorView { } } + /** + * Set the display name onto the text field directly. This does not affect the underlying + * data structure so it is similar to the user typing the value in on the field directly. + * + * @param name The name to set on the text field. + */ + public void setDisplayName(String name) { + // For now, assume the first text field is the name. + // TODO: Find a better way to get a hold of the name field. + super.setValue(0, name); + } + @Override protected Parcelable onSaveInstanceState() { SavedState state = new SavedState(super.onSaveInstanceState()); diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java index 0b47021d2..95eb0c162 100644 --- a/src/com/android/contacts/editor/TextFieldsEditorView.java +++ b/src/com/android/contacts/editor/TextFieldsEditorView.java @@ -174,6 +174,10 @@ public class TextFieldsEditorView extends LabeledEditorView { } } + public void setValue(int field, String value) { + mFieldEditTexts[field].setText(value); + } + @Override public void setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly, ViewIdGenerator vig) { diff --git a/src/com/android/contacts/model/RawContactDeltaList.java b/src/com/android/contacts/model/RawContactDeltaList.java index 600a7518f..1a9eb718c 100644 --- a/src/com/android/contacts/model/RawContactDeltaList.java +++ b/src/com/android/contacts/model/RawContactDeltaList.java @@ -49,17 +49,7 @@ public class RawContactDeltaList extends ArrayList<RawContactDelta> implements P private boolean mSplitRawContacts; private long[] mJoinWithRawContactIds; - private RawContactDeltaList() { - } - - /** - * Create an {@link RawContactDeltaList} that contains the given {@link RawContactDelta}, - * usually when inserting a new {@link Contacts} entry. - */ - public static RawContactDeltaList fromSingle(RawContactDelta delta) { - final RawContactDeltaList state = new RawContactDeltaList(); - state.add(delta); - return state; + public RawContactDeltaList() { } /** @@ -85,6 +75,11 @@ public class RawContactDeltaList extends ArrayList<RawContactDelta> implements P */ public static RawContactDeltaList fromIterator(Iterator<?> iterator) { final RawContactDeltaList state = new RawContactDeltaList(); + state.addAll(iterator); + return state; + } + + public void addAll(Iterator<?> iterator) { // Perform background query to pull contact details while (iterator.hasNext()) { // Read all contacts into local deltas to prepare for edits @@ -93,9 +88,8 @@ public class RawContactDeltaList extends ArrayList<RawContactDelta> implements P ? RawContact.createFrom((Entity) nextObject) : (RawContact) nextObject; final RawContactDelta rawContactDelta = RawContactDelta.fromBefore(before); - state.add(rawContactDelta); + add(rawContactDelta); } - return state; } /** diff --git a/tests/src/com/android/contacts/RawContactDeltaListTests.java b/tests/src/com/android/contacts/RawContactDeltaListTests.java index a8c445b88..6a75b818a 100644 --- a/tests/src/com/android/contacts/RawContactDeltaListTests.java +++ b/tests/src/com/android/contacts/RawContactDeltaListTests.java @@ -45,6 +45,7 @@ import com.google.common.collect.Lists; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Collections; /** * Tests for {@link RawContactDeltaList} which focus on "diff" operations that should @@ -112,10 +113,8 @@ public class RawContactDeltaListTests extends AndroidTestCase { } static RawContactDeltaList buildSet(RawContactDelta... deltas) { - final RawContactDeltaList set = RawContactDeltaList.fromSingle(deltas[0]); - for (int i = 1; i < deltas.length; i++) { - set.add(deltas[i]); - } + final RawContactDeltaList set = new RawContactDeltaList(); + Collections.addAll(set, deltas); return set; } diff --git a/tests/src/com/android/contacts/RawContactModifierTests.java b/tests/src/com/android/contacts/RawContactModifierTests.java index 91358ca37..ce69b55bc 100644 --- a/tests/src/com/android/contacts/RawContactModifierTests.java +++ b/tests/src/com/android/contacts/RawContactModifierTests.java @@ -521,7 +521,9 @@ public class RawContactModifierTests extends AndroidTestCase { // Try creating a contact without any child entries final RawContactDelta state = getRawContact(null); - final RawContactDeltaList set = RawContactDeltaList.fromSingle(state); + final RawContactDeltaList set = new RawContactDeltaList(); + set.add(state); + // Build diff, expecting single insert final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); @@ -549,7 +551,8 @@ public class RawContactModifierTests extends AndroidTestCase { // Try creating a contact with single empty entry final RawContactDelta state = getRawContact(null); RawContactModifier.insertChild(state, kindPhone, typeHome); - final RawContactDeltaList set = RawContactDeltaList.fromSingle(state); + final RawContactDeltaList set = new RawContactDeltaList(); + set.add(state); // Build diff, expecting two insert operations final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); @@ -593,7 +596,8 @@ public class RawContactModifierTests extends AndroidTestCase { second.put(Phone.NUMBER, TEST_PHONE); final RawContactDelta state = getRawContact(TEST_ID, first, second); - final RawContactDeltaList set = RawContactDeltaList.fromSingle(state); + final RawContactDeltaList set = new RawContactDeltaList(); + set.add(state); // Build diff, expecting no changes final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); @@ -658,7 +662,8 @@ public class RawContactModifierTests extends AndroidTestCase { first.put(Phone.NUMBER, TEST_PHONE); final RawContactDelta state = getRawContact(TEST_ID, first); - final RawContactDeltaList set = RawContactDeltaList.fromSingle(state); + final RawContactDeltaList set = new RawContactDeltaList(); + set.add(state); // Build diff, expecting no changes final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); |