Merge "Adding content description for photo overlay."
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index c47040d..f836139 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -999,25 +999,35 @@
}
} else {
// Build an IM Intent
- String host = im.getCustomProtocol();
-
- if (protocol != Im.PROTOCOL_CUSTOM) {
- // Try bringing in a well-known host for specific protocols
- host = ContactsUtils.lookupProviderNameFromId(protocol);
- }
-
- if (!TextUtils.isEmpty(host)) {
- final String authority = host.toLowerCase();
- final Uri imUri = new Uri.Builder().scheme(CallUtil.SCHEME_IMTO).authority(
- authority).appendPath(data).build();
- final Intent intent = new Intent(Intent.ACTION_SENDTO, imUri);
- if (PhoneCapabilityTester.isIntentRegistered(context, intent)) {
- entry.intent = intent;
- }
+ final Intent imIntent = getCustomIMIntent(im, protocol);
+ if (imIntent != null &&
+ PhoneCapabilityTester.isIntentRegistered(context, imIntent)) {
+ entry.intent = imIntent;
}
}
}
+ @VisibleForTesting
+ public static Intent getCustomIMIntent(ImDataItem im, int protocol) {
+ String host = im.getCustomProtocol();
+ final String data = im.getData();
+ if (TextUtils.isEmpty(data)) {
+ return null;
+ }
+ if (protocol != Im.PROTOCOL_CUSTOM) {
+ // Try bringing in a well-known host for specific protocols
+ host = ContactsUtils.lookupProviderNameFromId(protocol);
+ }
+ if (TextUtils.isEmpty(host)) {
+ return null;
+ }
+ final String authority = host.toLowerCase();
+ final Uri imUri = new Uri.Builder().scheme(CallUtil.SCHEME_IMTO).authority(
+ authority).appendPath(data).build();
+ final Intent intent = new Intent(Intent.ACTION_SENDTO, imUri);
+ return intent;
+ }
+
/**
* Show a list popup. Used for "popup-able" entry, such as "More networks".
*/
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 916ae70..da0d91e 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.activities.ContactEditorAccountsChangedActivity;
import com.android.contacts.activities.ContactEditorActivity;
import com.android.contacts.activities.JoinContactActivity;
-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.detail.PhotoSelectionHandler;
+import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
+import com.android.contacts.editor.Editor.EditorListener;
+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.model.RawContactDeltaList;
+import com.android.contacts.model.RawContactModifier;
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
}
}
mRequestFocus = true;
-
+ mExistingContactDataReady = true;
bindEditors();
}
@@ -649,8 +698,13 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
};
- 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 @@
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 @@
* performing user actions.
*/
private boolean hasValidState() {
- return mState != null && mState.size() > 0;
+ return mState.size() > 0;
}
/**
@@ -1117,7 +1176,7 @@
}
private boolean revert() {
- if (mState == null || !hasPendingChanges()) {
+ if (mState.isEmpty() || !hasPendingChanges()) {
doRevertAction();
} else {
CancelEditDialogFragment.show(this);
@@ -1193,7 +1252,7 @@
// 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 @@
* 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 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 @@
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 @@
@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 d069c8d..9099307 100644
--- a/src/com/android/contacts/editor/RawContactEditorView.java
+++ b/src/com/android/contacts/editor/RawContactEditorView.java
@@ -418,7 +418,7 @@
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 f709021..4d72598 100644
--- a/src/com/android/contacts/editor/StructuredNameEditorView.java
+++ b/src/com/android/contacts/editor/StructuredNameEditorView.java
@@ -200,6 +200,18 @@
}
}
+ /**
+ * 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 0b47021..95eb0c1 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -174,6 +174,10 @@
}
}
+ 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 600a751..1a9eb71 100644
--- a/src/com/android/contacts/model/RawContactDeltaList.java
+++ b/src/com/android/contacts/model/RawContactDeltaList.java
@@ -49,17 +49,7 @@
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 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 @@
? 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 a8c445b..6a75b81 100644
--- a/tests/src/com/android/contacts/RawContactDeltaListTests.java
+++ b/tests/src/com/android/contacts/RawContactDeltaListTests.java
@@ -45,6 +45,7 @@
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 @@
}
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 91358ca..ce69b55 100644
--- a/tests/src/com/android/contacts/RawContactModifierTests.java
+++ b/tests/src/com/android/contacts/RawContactModifierTests.java
@@ -521,7 +521,9 @@
// 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 @@
// 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 @@
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 @@
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();
diff --git a/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java b/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java
index 0b86912..1747add 100644
--- a/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java
+++ b/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java
@@ -105,10 +105,11 @@
ImDataItem im = (ImDataItem) DataItem.createFrom(values);
DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
- ContactDetailFragment.buildImActions(mContext, entry, im);
- assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
+ final Intent imIntent =
+ ContactDetailFragment.getCustomIMIntent(im, Im.PROTOCOL_CUSTOM);
+ assertEquals(Intent.ACTION_SENDTO, imIntent.getAction());
- final Uri data = entry.intent.getData();
+ final Uri data = imIntent.getData();
assertEquals("imto", data.getScheme());
assertEquals(TEST_PROTOCOL, data.getAuthority());
assertEquals(TEST_ADDRESS, data.getPathSegments().get(0));