Merge change 9503 into eclair-dev
* changes:
Extracts the header code from BaseContactCardActivity
diff --git a/src/com/android/contacts/model/ContactsSource.java b/src/com/android/contacts/model/ContactsSource.java
index c344711..b778f2a 100644
--- a/src/com/android/contacts/model/ContactsSource.java
+++ b/src/com/android/contacts/model/ContactsSource.java
@@ -150,6 +150,7 @@
this.iconRes = iconRes;
this.weight = weight;
this.editable = editable;
+ this.typeOverallMax = -1;
}
}
@@ -169,6 +170,7 @@
public EditType(int rawValue, int labelRes) {
this.rawValue = rawValue;
this.labelRes = labelRes;
+ this.specificMax = -1;
}
public EditType(int rawValue, int labelRes, boolean secondary) {
@@ -185,6 +187,20 @@
this(rawValue, labelRes, secondary, specificMax);
this.customColumn = customColumn;
}
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof EditType) {
+ final EditType other = (EditType)object;
+ return other.rawValue == rawValue;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return rawValue;
+ }
}
/**
diff --git a/src/com/android/contacts/model/AugmentedEntity.java b/src/com/android/contacts/model/EntityDelta.java
similarity index 75%
rename from src/com/android/contacts/model/AugmentedEntity.java
rename to src/com/android/contacts/model/EntityDelta.java
index 2cded4e..09e2e98 100644
--- a/src/com/android/contacts/model/AugmentedEntity.java
+++ b/src/com/android/contacts/model/EntityDelta.java
@@ -34,7 +34,7 @@
import java.util.Set;
/**
- * Contains an {@link Entity} that records any modifications separately so the
+ * Contains an {@link Entity} and records any modifications separately so the
* original {@link Entity} can be swapped out with a newer version and the
* changes still cleanly applied.
* <p>
@@ -46,55 +46,55 @@
* rows are missing from the new {@link Entity}, we know the original data must
* be deleted, but to preserve the user modifications we treat as an insert.
*/
-public class AugmentedEntity {
+public class EntityDelta {
// TODO: optimize by using contentvalues pool, since we allocate so many of them
/**
* Direct values from {@link Entity#getEntityValues()}.
*/
- private AugmentedValues mValues;
+ private ValuesDelta mValues;
/**
* Internal map of children values from {@link Entity#getSubValues()}, which
* we store here sorted into {@link Data#MIMETYPE} bins.
*/
- private HashMap<String, ArrayList<AugmentedValues>> mEntries;
+ private HashMap<String, ArrayList<ValuesDelta>> mEntries;
- private AugmentedEntity() {
- mEntries = new HashMap<String, ArrayList<AugmentedValues>>();
+ private EntityDelta() {
+ mEntries = new HashMap<String, ArrayList<ValuesDelta>>();
}
- public AugmentedEntity(AugmentedValues values) {
+ public EntityDelta(ValuesDelta values) {
this();
mValues = values;
}
/**
- * Build an {@link AugmentedEntity} using the given {@link Entity} as a
+ * Build an {@link EntityDelta} using the given {@link Entity} as a
* starting point; the "before" snapshot.
*/
- public static AugmentedEntity fromBefore(Entity before) {
- final AugmentedEntity entity = new AugmentedEntity();
- entity.mValues = AugmentedValues.fromBefore(before.getEntityValues());
- entity.mValues.setIdColumn(RawContacts._ID);
+ public static EntityDelta fromBefore(Entity before) {
+ final EntityDelta entity = new EntityDelta();
+ entity.mValues = ValuesDelta.fromBefore(before.getEntityValues());
+ entity.mValues.setIdColumn("raw_contacts", RawContacts._ID);
for (NamedContentValues namedValues : before.getSubValues()) {
- entity.addEntry(AugmentedValues.fromBefore(namedValues.values));
+ entity.addEntry(ValuesDelta.fromBefore(namedValues.values));
}
return entity;
}
- public AugmentedValues getValues() {
+ public ValuesDelta getValues() {
return mValues;
}
/**
- * Get the {@link AugmentedValues} child marked as {@link Data#IS_PRIMARY}.
+ * Get the {@link ValuesDelta} child marked as {@link Data#IS_PRIMARY}.
*/
- public AugmentedValues getPrimaryEntry(String mimeType) {
+ public ValuesDelta getPrimaryEntry(String mimeType) {
// TODO: handle the case where the caller must have a non-null value,
// for example inserting a displayname automatically
- final ArrayList<AugmentedValues> mimeEntries = getMimeEntries(mimeType, false);
- for (AugmentedValues entry : mimeEntries) {
+ final ArrayList<ValuesDelta> mimeEntries = getMimeEntries(mimeType, false);
+ for (ValuesDelta entry : mimeEntries) {
if (entry.isPrimary()) {
return entry;
}
@@ -103,19 +103,19 @@
}
/**
- * Return the list of child {@link AugmentedValues} from our optimized map,
+ * Return the list of child {@link ValuesDelta} from our optimized map,
* creating the list if requested.
*/
- private ArrayList<AugmentedValues> getMimeEntries(String mimeType, boolean lazyCreate) {
- ArrayList<AugmentedValues> mimeEntries = mEntries.get(mimeType);
+ private ArrayList<ValuesDelta> getMimeEntries(String mimeType, boolean lazyCreate) {
+ ArrayList<ValuesDelta> mimeEntries = mEntries.get(mimeType);
if (mimeEntries == null && lazyCreate) {
- mimeEntries = new ArrayList<AugmentedValues>();
+ mimeEntries = new ArrayList<ValuesDelta>();
mEntries.put(mimeType, mimeEntries);
}
return mimeEntries;
}
- public ArrayList<AugmentedValues> getMimeEntries(String mimeType) {
+ public ArrayList<ValuesDelta> getMimeEntries(String mimeType) {
return getMimeEntries(mimeType, false);
}
@@ -123,25 +123,25 @@
return mEntries.containsKey(mimeType);
}
- public void addEntry(AugmentedValues entry) {
+ public void addEntry(ValuesDelta entry) {
final String mimeType = entry.getMimetype();
getMimeEntries(mimeType, true).add(entry);
}
/**
- * Find the {@link AugmentedValues} that has a specific
+ * Find the {@link ValuesDelta} that has a specific
* {@link BaseColumns#_ID} value, used when {@link #augmentFrom(Parcel)} is
* inflating a modified state.
*/
- public AugmentedValues getEntry(Long childId) {
+ public ValuesDelta getEntry(Long childId) {
if (childId == null) {
// Requesting an "insert" entry, which has no "before"
return null;
}
// Search all children for requested entry
- for (ArrayList<AugmentedValues> mimeEntries : mEntries.values()) {
- for (AugmentedValues entry : mimeEntries) {
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta entry : mimeEntries) {
if (entry.getId() == childId) {
return entry;
}
@@ -150,6 +150,21 @@
return null;
}
+ /**
+ * Return the total number of {@link ValuesDelta} contained.
+ */
+ public int getEntryCount(boolean onlyVisible) {
+ int count = 0;
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta child : mimeEntries) {
+ // Skip deleted items when requesting only visible
+ if (onlyVisible && child.isVisible()) continue;
+ count++;
+ }
+ }
+ return count;
+ }
+
private static final int MODE_CONTINUE = 1;
private static final int MODE_DONE = 2;
@@ -163,7 +178,7 @@
final ContentValues after = (ContentValues)parcel.readValue(null);
if (mValues == null) {
// Entity didn't exist before, so "insert"
- mValues = AugmentedValues.fromAfter(after);
+ mValues = ValuesDelta.fromAfter(after);
} else {
// Existing entity "update"
mValues.mAfter = after;
@@ -176,10 +191,10 @@
final Long childId = readLong(parcel);
final ContentValues after = (ContentValues)parcel.readValue(null);
- AugmentedValues entry = getEntry(childId);
+ ValuesDelta entry = getEntry(childId);
if (entry == null) {
// Is "insert", or "before" record is missing, so now "insert"
- entry = AugmentedValues.fromAfter(after);
+ entry = ValuesDelta.fromAfter(after);
addEntry(entry);
} else {
// Existing entry "update"
@@ -196,8 +211,8 @@
public void augmentTo(Parcel parcel) {
parcel.writeValue(mValues.mAfter);
- for (ArrayList<AugmentedValues> mimeEntries : mEntries.values()) {
- for (AugmentedValues child : mimeEntries) {
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta child : mimeEntries) {
parcel.writeInt(MODE_CONTINUE);
writeLong(parcel, child.getId());
parcel.writeValue(child.mAfter);
@@ -217,14 +232,14 @@
@Override
public boolean equals(Object object) {
- if (object instanceof AugmentedEntity) {
- final AugmentedEntity other = (AugmentedEntity)object;
+ if (object instanceof EntityDelta) {
+ final EntityDelta other = (EntityDelta)object;
// Equality failed if parent values different
if (!other.mValues.equals(mValues)) return false;
- for (ArrayList<AugmentedValues> mimeEntries : mEntries.values()) {
- for (AugmentedValues child : mimeEntries) {
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta child : mimeEntries) {
// Equality failed if any children unmatched
if (!other.containsEntry(child)) return false;
}
@@ -236,9 +251,9 @@
return false;
}
- private boolean containsEntry(AugmentedValues entry) {
- for (ArrayList<AugmentedValues> mimeEntries : mEntries.values()) {
- for (AugmentedValues child : mimeEntries) {
+ private boolean containsEntry(ValuesDelta entry) {
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta child : mimeEntries) {
// Contained if we find any child that matches
if (child.equals(entry)) return true;
}
@@ -252,8 +267,8 @@
builder.append("\n(");
builder.append(mValues.toString());
builder.append(") = {");
- for (ArrayList<AugmentedValues> mimeEntries : mEntries.values()) {
- for (AugmentedValues child : mimeEntries) {
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta child : mimeEntries) {
builder.append("\n\t");
child.toString(builder);
}
@@ -271,7 +286,7 @@
/**
* Build a list of {@link ContentProviderOperation} that will transform the
* current "before" {@link Entity} state into the modified state which this
- * {@link AugmentedEntity} represents.
+ * {@link EntityDelta} represents.
*/
public ArrayList<ContentProviderOperation> buildDiff() {
final ArrayList<ContentProviderOperation> diff = new ArrayList<ContentProviderOperation>();
@@ -287,8 +302,8 @@
possibleAdd(diff, builder);
// Build operations for all children
- for (ArrayList<AugmentedValues> mimeEntries : mEntries.values()) {
- for (AugmentedValues child : mimeEntries) {
+ for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
+ for (ValuesDelta child : mimeEntries) {
// Ignore children if parent was deleted
if (isContactDelete) continue;
@@ -327,31 +342,32 @@
* modified version of that state. This allows us to build insert, update,
* or delete operations based on a "before" {@link Entity} snapshot.
*/
- public static class AugmentedValues {
+ public static class ValuesDelta {
private ContentValues mBefore;
private ContentValues mAfter;
+ private String mIdTable = "data";
private String mIdColumn = BaseColumns._ID;
- private AugmentedValues() {
+ private ValuesDelta() {
}
/**
- * Create {@link AugmentedValues}, using the given object as the
+ * Create {@link ValuesDelta}, using the given object as the
* "before" state, usually from an {@link Entity}.
*/
- public static AugmentedValues fromBefore(ContentValues before) {
- final AugmentedValues entry = new AugmentedValues();
+ public static ValuesDelta fromBefore(ContentValues before) {
+ final ValuesDelta entry = new ValuesDelta();
entry.mBefore = before;
entry.mAfter = new ContentValues();
return entry;
}
/**
- * Create {@link AugmentedValues}, using the given object as the "after"
+ * Create {@link ValuesDelta}, using the given object as the "after"
* state, usually when we are inserting a row instead of updating.
*/
- public static AugmentedValues fromAfter(ContentValues after) {
- final AugmentedValues entry = new AugmentedValues();
+ public static ValuesDelta fromAfter(ContentValues after) {
+ final ValuesDelta entry = new ValuesDelta();
entry.mBefore = null;
entry.mAfter = after;
return entry;
@@ -385,7 +401,9 @@
return getAsLong(mIdColumn);
}
- public void setIdColumn(String idColumn) {
+ public void setIdColumn(String idTable, String idColumn) {
+ // TODO: remove idTable when we've fixed contentprovider
+ mIdTable = idTable;
mIdColumn = idColumn;
}
@@ -393,19 +411,28 @@
return (getAsLong(Data.IS_PRIMARY) != 0);
}
+ public boolean beforeExists() {
+ return (mBefore != null && mBefore.containsKey(mIdColumn));
+ }
+
+ public boolean isVisible() {
+ // When "after" is present, then visible
+ return (mAfter != null);
+ }
+
public boolean isDelete() {
// When "after" is wiped, action is "delete"
- return (mAfter == null);
+ return beforeExists() && (mAfter == null);
}
public boolean isUpdate() {
// When "after" has some changes, action is "update"
- return (mAfter.size() > 0);
+ return beforeExists() && (mAfter.size() > 0);
}
public boolean isInsert() {
- // When no "before" id, action is "insert"
- return mBefore == null || getId() == null;
+ // When no "before" id, and has "after", action is "insert"
+ return !beforeExists() && (mAfter != null);
}
public void markDeleted() {
@@ -454,9 +481,9 @@
@Override
public boolean equals(Object object) {
- if (object instanceof AugmentedValues) {
+ if (object instanceof ValuesDelta) {
// Only exactly equal with both are identical subsets
- final AugmentedValues other = (AugmentedValues)object;
+ final ValuesDelta other = (ValuesDelta)object;
return this.subsetEquals(other) && other.subsetEquals(this);
}
return false;
@@ -485,10 +512,10 @@
}
/**
- * Check if the given {@link AugmentedValues} is both a subset of this
+ * Check if the given {@link ValuesDelta} is both a subset of this
* object, and any defined keys have equal values.
*/
- public boolean subsetEquals(AugmentedValues other) {
+ public boolean subsetEquals(ValuesDelta other) {
for (String key : this.keySet()) {
final String ourValue = this.getAsString(key);
final String theirValue = other.getAsString(key);
@@ -518,11 +545,11 @@
} else if (isDelete()) {
// When marked for deletion and "before" exists, then "delete"
builder = ContentProviderOperation.newDelete(targetUri);
- builder.withSelection(mIdColumn + "=" + getId(), null);
+ builder.withSelection(mIdTable + "." + mIdColumn + "=" + getId(), null);
} else if (isUpdate()) {
// When has changes and "before" exists, then "update"
builder = ContentProviderOperation.newUpdate(targetUri);
- builder.withSelection(mIdColumn + "=" + getId(), null);
+ builder.withSelection(mIdTable + "." + mIdColumn + "=" + getId(), null);
builder.withValues(mAfter);
}
return builder;
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index e68d0dc..5dd2880 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -16,41 +16,134 @@
package com.android.contacts.model;
-import com.android.contacts.model.AugmentedEntity.AugmentedValues;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
import com.android.contacts.model.ContactsSource.DataKind;
import com.android.contacts.model.ContactsSource.EditType;
import android.content.ContentValues;
import android.provider.ContactsContract.Data;
+import android.util.SparseIntArray;
+import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
/**
- * Helper methods for modifying an {@link AugmentedEntity}, such as inserting
+ * Helper methods for modifying an {@link EntityDelta}, such as inserting
* new rows, or enforcing {@link ContactsSource}.
*/
public class EntityModifier {
- // TODO: provide helper to force an augmentedentity into sourceconstraints?
-
/**
- * For the given {@link AugmentedEntity}, determine if the given
+ * For the given {@link EntityDelta}, determine if the given
* {@link DataKind} could be inserted under specific
* {@link ContactsSource}.
*/
- public static boolean canInsert(AugmentedEntity contact, DataKind kind) {
- // TODO: compare against constraints to determine if insert is possible
- return true;
+ public static boolean canInsert(EntityDelta state, DataKind kind) {
+ // Insert possible when have valid types and under overall maximum
+ final boolean validTypes = hasValidTypes(state, kind);
+ final boolean validOverall = (kind.typeOverallMax == -1)
+ || (state.getEntryCount(true) < kind.typeOverallMax);
+ return (validTypes && validOverall);
+ }
+
+ public static boolean hasValidTypes(EntityDelta state, DataKind kind) {
+ return (getValidTypes(state, kind).size() > 0);
}
/**
- * For the given {@link AugmentedEntity} and {@link DataKind}, return the
+ * For the given {@link EntityDelta} and {@link DataKind}, return the
* list possible {@link EditType} options available based on
* {@link ContactsSource}.
*/
- public static List<EditType> getValidTypes(AugmentedEntity entity, DataKind kind,
+ public static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind) {
+ return getValidTypes(state, kind, null, true, null);
+ }
+
+ /**
+ * For the given {@link EntityDelta} and {@link DataKind}, return the
+ * list possible {@link EditType} options available based on
+ * {@link ContactsSource}.
+ *
+ * @param forceInclude Always include this {@link EditType} in the returned
+ * list, even when an otherwise-invalid choice. This is useful
+ * when showing a dialog that includes the current type.
+ */
+ public static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind,
EditType forceInclude) {
- // TODO: enforce constraints and include any extra provided
- return kind.typeList;
+ return getValidTypes(state, kind, forceInclude, true, null);
+ }
+
+ /**
+ * For the given {@link EntityDelta} and {@link DataKind}, return the
+ * list possible {@link EditType} options available based on
+ * {@link ContactsSource}.
+ *
+ * @param forceInclude Always include this {@link EditType} in the returned
+ * list, even when an otherwise-invalid choice. This is useful
+ * when showing a dialog that includes the current type.
+ * @param includeSecondary If true, include any valid types marked as
+ * {@link EditType#secondary}.
+ * @param typeCount When provided, will be used for the frequency count of
+ * each {@link EditType}, otherwise built using
+ * {@link #getTypeFrequencies(EntityDelta, DataKind)}.
+ */
+ private static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind,
+ EditType forceInclude, boolean includeSecondary, SparseIntArray typeCount) {
+ final ArrayList<EditType> validTypes = new ArrayList<EditType>();
+
+ // Bail early if no types provided
+ if (!hasEditTypes(kind)) return validTypes;
+
+ if (typeCount == null) {
+ // Build frequency counts if not provided
+ typeCount = getTypeFrequencies(state, kind);
+ }
+
+ // Build list of valid types
+ final int overallCount = typeCount.get(FREQUENCY_TOTAL);
+ for (EditType type : kind.typeList) {
+ final boolean validUnlimited = (type.specificMax == -1 && overallCount < kind.typeOverallMax);
+ final boolean validSpecific = (typeCount.get(type.rawValue) < type.specificMax);
+ final boolean validSecondary = (includeSecondary ? true : !type.secondary);
+ final boolean forcedInclude = type.equals(forceInclude);
+ if (forcedInclude || (validSecondary && (validUnlimited || validSpecific))) {
+ // Type is valid when no limit, under limit, or forced include
+ validTypes.add(type);
+ }
+ }
+
+ return validTypes;
+ }
+
+ private static final int FREQUENCY_TOTAL = Integer.MIN_VALUE;
+
+ /**
+ * Count up the frequency that each {@link EditType} appears in the given
+ * {@link EntityDelta}. The returned {@link SparseIntArray} maps from
+ * {@link EditType#rawValue} to counts, with the total overall count stored
+ * as {@link #FREQUENCY_TOTAL}.
+ */
+ private static SparseIntArray getTypeFrequencies(EntityDelta state, DataKind kind) {
+ final SparseIntArray typeCount = new SparseIntArray();
+
+ // Find all entries for this kind, bailing early if none found
+ final List<ValuesDelta> mimeEntries = state.getMimeEntries(kind.mimeType);
+ if (mimeEntries == null) return typeCount;
+
+ int totalCount = 0;
+ for (ValuesDelta entry : mimeEntries) {
+ // Only count visible entries
+ if (!entry.isVisible()) continue;
+ totalCount++;
+
+ final EditType type = getCurrentType(entry, kind);
+ if (type != null) {
+ final int count = typeCount.get(type.rawValue);
+ typeCount.put(type.rawValue, count + 1);
+ }
+ }
+ typeCount.put(FREQUENCY_TOTAL, totalCount);
+ return typeCount;
}
/**
@@ -63,11 +156,19 @@
/**
* Find the {@link EditType} that describes the given
- * {@link AugmentedValues} row, assuming the given {@link DataKind} dictates
+ * {@link ValuesDelta} row, assuming the given {@link DataKind} dictates
* the possible types.
*/
- public static EditType getCurrentType(AugmentedValues entry, DataKind kind) {
- final long rawValue = entry.getAsLong(kind.typeColumn);
+ public static EditType getCurrentType(ValuesDelta entry, DataKind kind) {
+ final Long rawValue = entry.getAsLong(kind.typeColumn);
+ if (rawValue == null) return null;
+ return getType(kind, rawValue.intValue());
+ }
+
+ /**
+ * Find the {@link EditType} with the given {@link EditType#rawValue}.
+ */
+ public static EditType getType(DataKind kind, int rawValue) {
for (EditType type : kind.typeList) {
if (type.rawValue == rawValue) {
return type;
@@ -77,11 +178,64 @@
}
/**
- * Insert a new child of kind {@link DataKind} into the given
- * {@link AugmentedEntity}. Assumes the caller has already checked
- * {@link #canInsert(AugmentedEntity, DataKind)}.
+ * Find the best {@link EditType} for a potential insert. The "best" is the
+ * first primary type that doesn't already exist. When all valid types
+ * exist, we pick the last valid option.
*/
- public static void insertChild(AugmentedEntity state, DataKind kind) {
+ public static EditType getBestValidType(EntityDelta state, DataKind kind,
+ boolean includeSecondary) {
+ // Shortcut when no types
+ if (kind.typeColumn == null) return null;
+
+ // Find type counts and valid primary types, bail if none
+ final SparseIntArray typeCount = getTypeFrequencies(state, kind);
+ final ArrayList<EditType> validTypes = getValidTypes(state, kind, null, includeSecondary,
+ typeCount);
+ if (validTypes.size() == 0) return null;
+
+ // Keep track of the last valid type
+ final EditType lastType = validTypes.get(validTypes.size() - 1);
+
+ // Remove any types that already exist
+ Iterator<EditType> iterator = validTypes.iterator();
+ while (iterator.hasNext()) {
+ final EditType type = iterator.next();
+ final int count = typeCount.get(type.rawValue);
+
+ if (count > 0) {
+ // Type already appears, so don't consider
+ iterator.remove();
+ }
+ }
+
+ // Use the best remaining, otherwise the last valid
+ if (validTypes.size() > 0) {
+ return validTypes.get(0);
+ } else {
+ return lastType;
+ }
+ }
+
+ /**
+ * Insert a new child of kind {@link DataKind} into the given
+ * {@link EntityDelta}. Tries using the best {@link EditType} found
+ * using {@link #getBestValidType(EntityDelta, DataKind, boolean)}.
+ */
+ public static void insertChild(EntityDelta state, DataKind kind) {
+ // First try finding a valid primary
+ EditType bestType = getBestValidType(state, kind, false);
+ if (bestType == null) {
+ // No valid primary found, so expand search to secondary
+ bestType = getBestValidType(state, kind, true);
+ }
+ insertChild(state, kind, bestType);
+ }
+
+ /**
+ * Insert a new child of kind {@link DataKind} into the given
+ * {@link EntityDelta}, marked with the given {@link EditType}.
+ */
+ public static void insertChild(EntityDelta state, DataKind kind, EditType type) {
final ContentValues after = new ContentValues();
// Our parent CONTACT_ID is provided later
@@ -92,13 +246,12 @@
after.putAll(kind.defaultValues);
}
- if (kind.typeColumn != null) {
- // TODO: add the best-kind of entry based on current state machine
- final EditType firstType = kind.typeList.get(0);
- after.put(kind.typeColumn, firstType.rawValue);
+ if (kind.typeColumn != null && type != null) {
+ // Set type, if provided
+ after.put(kind.typeColumn, type.rawValue);
}
- state.addEntry(AugmentedValues.fromAfter(after));
+ state.addEntry(ValuesDelta.fromAfter(after));
}
}
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
index 7555b20..94a67bf 100644
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -17,7 +17,7 @@
package com.android.contacts.ui;
import com.android.contacts.R;
-import com.android.contacts.model.AugmentedEntity;
+import com.android.contacts.model.EntityDelta;
import com.android.contacts.model.ContactsSource;
import com.android.contacts.model.Sources;
import com.android.contacts.ui.widget.ContactEditorView;
@@ -78,7 +78,7 @@
// private boolean mContactChanged = false;
private Uri mUri;
- private ArrayList<AugmentedEntity> mEntities = new ArrayList<AugmentedEntity>();
+ private ArrayList<EntityDelta> mEntities = new ArrayList<EntityDelta>();
private ContentResolver mResolver;
private ContactEditorView mEditor;
@@ -128,7 +128,7 @@
ContactsContract.RawContacts.CONTACT_ID + "=" + aggId, null, null);
while (iterator.hasNext()) {
final Entity before = iterator.next();
- final AugmentedEntity entity = AugmentedEntity.fromBefore(before);
+ final EntityDelta entity = EntityDelta.fromBefore(before);
mEntities.add(entity);
@@ -555,7 +555,7 @@
final ContentResolver resolver = this.getContentResolver();
- for (AugmentedEntity entity : mEntities) {
+ for (EntityDelta entity : mEntities) {
Log.d(TAG, "about to persist " + entity.toString());
diff --git a/src/com/android/contacts/ui/widget/ContactEditorView.java b/src/com/android/contacts/ui/widget/ContactEditorView.java
index 924241f..5e7785a 100644
--- a/src/com/android/contacts/ui/widget/ContactEditorView.java
+++ b/src/com/android/contacts/ui/widget/ContactEditorView.java
@@ -17,10 +17,10 @@
package com.android.contacts.ui.widget;
import com.android.contacts.R;
-import com.android.contacts.model.AugmentedEntity;
+import com.android.contacts.model.EntityDelta;
import com.android.contacts.model.EntityModifier;
import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.AugmentedEntity.AugmentedValues;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
import com.android.contacts.model.ContactsSource.DataKind;
import com.android.contacts.model.ContactsSource.EditField;
import com.android.contacts.model.ContactsSource.EditType;
@@ -50,11 +50,11 @@
/**
* Custom view that provides all the editor interaction for a specific
- * {@link Contacts} represented through an {@link AugmentedEntity}. Callers can
+ * {@link Contacts} represented through an {@link EntityDelta}. Callers can
* reuse this view and quickly rebuild its contents through
- * {@link #setState(AugmentedEntity, ContactsSource)}.
+ * {@link #setState(EntityDelta, ContactsSource)}.
* <p>
- * Internal updates are performed against {@link AugmentedValues} so that the
+ * Internal updates are performed against {@link ValuesDelta} so that the
* source {@link Entity} can be swapped out. Any state-based changes, such as
* adding {@link Data} rows or changing {@link EditType}, are performed through
* {@link EntityModifier} to ensure that {@link ContactsSource} are enforced.
@@ -80,10 +80,10 @@
/**
* Set the internal state for this view, given a current
- * {@link AugmentedEntity} state and the {@link ContactsSource} that
+ * {@link EntityDelta} state and the {@link ContactsSource} that
* apply to that state.
*/
- public void setState(AugmentedEntity state, ContactsSource source) {
+ public void setState(EntityDelta state, ContactsSource source) {
// Remove any existing sections
mGeneral.removeAllViews();
mSecondary.removeAllViews();
@@ -96,11 +96,11 @@
final String mimeType = kind.mimeType;
if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
// Handle special case editor for structured name
- final AugmentedValues primary = state.getPrimaryEntry(mimeType);
+ final ValuesDelta primary = state.getPrimaryEntry(mimeType);
mDisplayName.setValues(null, primary, state);
} else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
// Handle special case editor for photos
- final AugmentedValues firstValue = state.getPrimaryEntry(mimeType);
+ final ValuesDelta firstValue = state.getPrimaryEntry(mimeType);
mPhoto.setValues(null, firstValue, state);
} else {
// Otherwise use generic section-based editors
@@ -120,7 +120,7 @@
* {@link DataKind} around a {@link Data#MIMETYPE}. This view shows a
* section header and a trigger for adding new {@link Data} rows.
*/
- public static class KindSection extends ViewHolder implements OnClickListener {
+ protected static class KindSection extends ViewHolder implements OnClickListener, EditorListener {
private static final int RES_SECTION = R.layout.item_edit_kind;
private ViewGroup mEditors;
@@ -128,9 +128,9 @@
private TextView mTitle;
private DataKind mKind;
- private AugmentedEntity mState;
+ private EntityDelta mState;
- public KindSection(Context context, DataKind kind, AugmentedEntity state) {
+ public KindSection(Context context, DataKind kind, EntityDelta state) {
super(context, RES_SECTION);
mKind = kind;
@@ -144,7 +144,12 @@
mTitle = (TextView)mContent.findViewById(R.id.kind_title);
mTitle.setText(kind.titleRes);
- rebuildFromState();
+ this.rebuildFromState();
+ this.updateAddEnabled();
+ }
+
+ public void onDeleted(Editor editor) {
+ this.updateAddEnabled();
}
/**
@@ -152,46 +157,71 @@
*/
public void rebuildFromState() {
// TODO: build special "stub" entries to help enter first-phone or first-email
- // TODO: set the add-enabled state based on entitymodifier
// Remove any existing editors
mEditors.removeAllViews();
// Build individual editors for each entry
if (!mState.hasMimeEntries(mKind.mimeType)) return;
- for (AugmentedValues entry : mState.getMimeEntries(mKind.mimeType)) {
+ for (ValuesDelta entry : mState.getMimeEntries(mKind.mimeType)) {
+ // Skip entries that aren't visible
+ if (!entry.isVisible()) continue;
+
final GenericEditor editor = new GenericEditor(mContext);
editor.setValues(mKind, entry, mState);
+ editor.setEditorListener(this);
mEditors.addView(editor.getView());
}
}
+ protected void updateAddEnabled() {
+ // Set enabled state on the "add" view
+ final boolean canInsert = EntityModifier.canInsert(mState, mKind);
+ mAdd.setEnabled(canInsert);
+ }
+
public void onClick(View v) {
// Insert a new child and rebuild
EntityModifier.insertChild(mState, mKind);
- rebuildFromState();
+ this.rebuildFromState();
+ this.updateAddEnabled();
}
}
/**
* Generic definition of something that edits a {@link Data} row through an
- * {@link AugmentedValues} object.
+ * {@link ValuesDelta} object.
*/
- public interface Editor {
+ protected interface Editor {
/**
- * Prepare this editor for the given {@link AugmentedValues}, which
+ * Prepare this editor for the given {@link ValuesDelta}, which
* builds any needed views. Any changes performed by the user will be
* written back to that same object.
*/
- public void setValues(DataKind kind, AugmentedValues values, AugmentedEntity state);
+ public void setValues(DataKind kind, ValuesDelta values, EntityDelta state);
+
+ /**
+ * Add a specific {@link EditorListener} to this {@link Editor}.
+ */
+ public void setEditorListener(EditorListener listener);
+ }
+
+ /**
+ * Listener for an {@link Editor}, usually to handle deleted items.
+ */
+ protected interface EditorListener {
+ /**
+ * Called when the given {@link Editor} has been deleted.
+ */
+ public void onDeleted(Editor editor);
}
/**
* Simple editor that handles labels and any {@link EditField} defined for
- * the entry. Uses {@link AugmentedValues} to read any existing
+ * the entry. Uses {@link ValuesDelta} to read any existing
* {@link Entity} values, and to correctly write any changes values.
*/
- public static class GenericEditor extends ViewHolder implements Editor, OnClickListener {
+ protected static class GenericEditor extends ViewHolder implements Editor, OnClickListener {
private static final int RES_EDITOR = R.layout.item_editor;
private static final int RES_FIELD = R.layout.item_editor_field;
private static final int RES_LABEL_ITEM = android.R.layout.simple_list_item_1;
@@ -201,8 +231,8 @@
private View mDelete;
private DataKind mKind;
- private AugmentedValues mEntry;
- private AugmentedEntity mState;
+ private ValuesDelta mEntry;
+ private EntityDelta mState;
private EditType mType;
@@ -218,6 +248,12 @@
mDelete.setOnClickListener(this);
}
+ private EditorListener mListener;
+
+ public void setEditorListener(EditorListener listener) {
+ mListener = listener;
+ }
+
/**
* Build the current label state based on selected {@link EditType} and
* possible custom label string.
@@ -242,12 +278,12 @@
mLabel.setText(mType.labelRes);
}
- public void setValues(DataKind kind, AugmentedValues entry, AugmentedEntity state) {
+ public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state) {
mKind = kind;
mEntry = entry;
mState = state;
- if (entry.isDelete()) {
+ if (!entry.isVisible()) {
// Hide ourselves entirely if deleted
mContent.setVisibility(View.GONE);
return;
@@ -386,6 +422,11 @@
// Mark as deleted and hide this editor
mEntry.markDeleted();
mContent.setVisibility(View.GONE);
+
+ if (mListener != null) {
+ // Notify listener when present
+ mListener.onDeleted(this);
+ }
break;
}
}
@@ -395,7 +436,7 @@
/**
* Simple editor for {@link Photo}.
*/
- public static class PhotoEditor extends ViewHolder implements Editor {
+ protected static class PhotoEditor extends ViewHolder implements Editor {
private static final int RES_PHOTO = R.layout.item_editor_photo;
public PhotoEditor(Context context) {
@@ -452,21 +493,27 @@
// setPhotoPresent(false);
// }
- public void setValues(DataKind kind, AugmentedValues values, AugmentedEntity state) {
+ public void setValues(DataKind kind, ValuesDelta values, EntityDelta state) {
+ }
+
+ public void setEditorListener(EditorListener listener) {
}
}
/**
* Simple editor for {@link StructuredName}.
*/
- public static class DisplayNameEditor extends ViewHolder implements Editor {
+ protected static class DisplayNameEditor extends ViewHolder implements Editor {
private static final int RES_DISPLAY_NAME = R.layout.item_editor_displayname;
public DisplayNameEditor(Context context) {
super(context, RES_DISPLAY_NAME);
}
- public void setValues(DataKind kind, AugmentedValues values, AugmentedEntity state) {
+ public void setValues(DataKind kind, ValuesDelta values, EntityDelta state) {
+ }
+
+ public void setEditorListener(EditorListener listener) {
}
}
diff --git a/tests/src/com/android/contacts/AugmentedEntityTests.java b/tests/src/com/android/contacts/EntityDeltaTests.java
similarity index 75%
rename from tests/src/com/android/contacts/AugmentedEntityTests.java
rename to tests/src/com/android/contacts/EntityDeltaTests.java
index e9498e1..791f311 100644
--- a/tests/src/com/android/contacts/AugmentedEntityTests.java
+++ b/tests/src/com/android/contacts/EntityDeltaTests.java
@@ -16,8 +16,8 @@
package com.android.contacts;
-import com.android.contacts.model.AugmentedEntity;
-import com.android.contacts.model.AugmentedEntity.AugmentedValues;
+import com.android.contacts.model.EntityDelta;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
import static android.content.ContentProviderOperation.TYPE_INSERT;
import static android.content.ContentProviderOperation.TYPE_UPDATE;
@@ -38,12 +38,12 @@
import java.util.ArrayList;
/**
- * Tests for {@link AugmentedEntity} and {@link AugmentedValues}. These tests
+ * Tests for {@link EntityDelta} and {@link ValuesDelta}. These tests
* focus on passing changes across {@link Parcel}, and verifying that they
* correctly build expected "diff" operations.
*/
@LargeTest
-public class AugmentedEntityTests extends AndroidTestCase {
+public class EntityDeltaTests extends AndroidTestCase {
public static final String TAG = "AugmentedEntityTests";
private static final long TEST_CONTACT_ID = 12;
@@ -54,7 +54,7 @@
private static final String TEST_ACCOUNT_NAME = "TEST";
- public AugmentedEntityTests() {
+ public EntityDeltaTests() {
super();
}
@@ -63,7 +63,7 @@
mContext = getContext();
}
- public Entity getEntity(long contactId, long phoneId) {
+ protected Entity getEntity(long contactId, long phoneId) {
// Build an existing contact read from database
final ContentValues contact = new ContentValues();
contact.put(RawContacts.VERSION, 43);
@@ -81,91 +81,68 @@
}
/**
- * Test that {@link AugmentedEntity#augmentTo(Parcel)} correctly passes any
+ * Test that {@link EntityDelta#augmentTo(Parcel)} correctly passes any
* changes through the {@link Parcel} object. This enforces that
- * {@link AugmentedEntity} should be identical when serialized against the
+ * {@link EntityDelta} should be identical when serialized against the
* same "before" {@link Entity}.
*/
public void testParcelChangesNone() {
final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
- final AugmentedEntity source = AugmentedEntity.fromBefore(before);
+ final EntityDelta source = EntityDelta.fromBefore(before);
+ final EntityDelta dest = EntityDelta.fromBefore(before);
- // No changes, should be same
- final Parcel parcel = Parcel.obtain();
- source.augmentTo(parcel);
-
- final AugmentedEntity dest = AugmentedEntity.fromBefore(before);
- parcel.setDataPosition(0);
- dest.augmentFrom(parcel);
-
- // Assert that we have same data rows
- assertEquals("Changed when passing through Parcel", source, dest);
- parcel.recycle();
+ // Merge modified values and assert they match
+ dest.mergeAfter(source);
+ assertEquals("Unexpected change when merging", source, dest);
}
public void testParcelChangesInsert() {
final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
- final AugmentedEntity source = AugmentedEntity.fromBefore(before);
+ final EntityDelta source = EntityDelta.fromBefore(before);
+ final EntityDelta dest = EntityDelta.fromBefore(before);
// Add a new row and pass across parcel, should be same
final ContentValues phone = new ContentValues();
phone.put(Data.MIMETYPE, Phone.MIMETYPE);
phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
phone.put(Phone.TYPE, Phone.TYPE_WORK);
- source.addEntry(AugmentedValues.fromAfter(phone));
+ source.addEntry(ValuesDelta.fromAfter(phone));
- final Parcel parcel = Parcel.obtain();
- source.augmentTo(parcel);
-
- final AugmentedEntity dest = AugmentedEntity.fromBefore(before);
- parcel.setDataPosition(0);
- dest.augmentFrom(parcel);
-
- // Assert that we have same data rows
- assertEquals("Changed when passing through Parcel", source, dest);
- parcel.recycle();
+ // Merge modified values and assert they match
+ dest.mergeAfter(source);
+ assertEquals("Unexpected change when merging", source, dest);
}
public void testParcelChangesUpdate() {
// Update existing row and pass across parcel, should be same
final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
- final AugmentedEntity source = AugmentedEntity.fromBefore(before);
- final AugmentedValues child = source.getEntry(TEST_PHONE_ID);
+ final EntityDelta source = EntityDelta.fromBefore(before);
+ final EntityDelta dest = EntityDelta.fromBefore(before);
+
+ final ValuesDelta child = source.getEntry(TEST_PHONE_ID);
child.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
- final Parcel parcel = Parcel.obtain();
- source.augmentTo(parcel);
-
- final AugmentedEntity dest = AugmentedEntity.fromBefore(before);
- parcel.setDataPosition(0);
- dest.augmentFrom(parcel);
-
- // Assert that we have same data rows
- assertEquals("Changed when passing through Parcel", source, dest);
- parcel.recycle();
+ // Merge modified values and assert they match
+ dest.mergeAfter(source);
+ assertEquals("Unexpected change when merging", source, dest);
}
public void testParcelChangesDelete() {
// Delete a row and pass across parcel, should be same
final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
- final AugmentedEntity source = AugmentedEntity.fromBefore(before);
- final AugmentedValues child = source.getEntry(TEST_PHONE_ID);
+ final EntityDelta source = EntityDelta.fromBefore(before);
+ final EntityDelta dest = EntityDelta.fromBefore(before);
+
+ final ValuesDelta child = source.getEntry(TEST_PHONE_ID);
child.markDeleted();
- final Parcel parcel = Parcel.obtain();
- source.augmentTo(parcel);
-
- final AugmentedEntity dest = AugmentedEntity.fromBefore(before);
- parcel.setDataPosition(0);
- dest.augmentFrom(parcel);
-
- // Assert that we have same data rows
- assertEquals("Changed when passing through Parcel", source, dest);
- parcel.recycle();
+ // Merge modified values and assert they match
+ dest.mergeAfter(source);
+ assertEquals("Unexpected change when merging", source, dest);
}
/**
- * Test that {@link AugmentedValues#buildDiff()} is correctly built for
+ * Test that {@link ValuesDelta#buildDiff()} is correctly built for
* insert, update, and delete cases. Note this only tests behavior for
* individual {@link Data} rows.
*/
@@ -174,7 +151,7 @@
before.put(Data._ID, TEST_PHONE_ID);
before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1);
- final AugmentedValues values = AugmentedValues.fromBefore(before);
+ final ValuesDelta values = ValuesDelta.fromBefore(before);
// None action shouldn't produce a builder
final Builder builder = values.buildDiff(Data.CONTENT_URI);
@@ -185,7 +162,7 @@
final ContentValues after = new ContentValues();
after.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
- final AugmentedValues values = AugmentedValues.fromAfter(after);
+ final ValuesDelta values = ValuesDelta.fromAfter(after);
// Should produce an insert action
final Builder builder = values.buildDiff(Data.CONTENT_URI);
@@ -198,7 +175,7 @@
before.put(Data._ID, TEST_PHONE_ID);
before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1);
- final AugmentedValues values = AugmentedValues.fromBefore(before);
+ final ValuesDelta values = ValuesDelta.fromBefore(before);
values.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
// Should produce an update action
@@ -212,7 +189,7 @@
before.put(Data._ID, TEST_PHONE_ID);
before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1);
- final AugmentedValues values = AugmentedValues.fromBefore(before);
+ final ValuesDelta values = ValuesDelta.fromBefore(before);
values.markDeleted();
// Should produce a delete action
@@ -222,13 +199,13 @@
}
/**
- * Test that {@link AugmentedEntity#buildDiff()} is correctly built for
+ * Test that {@link EntityDelta#buildDiff()} is correctly built for
* insert, update, and delete cases. This only tests a subset of possible
* {@link Data} row changes.
*/
public void testEntityDiffNone() {
final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
- final AugmentedEntity source = AugmentedEntity.fromBefore(before);
+ final EntityDelta source = EntityDelta.fromBefore(before);
// Assert that writing unchanged produces few operations
final ArrayList<ContentProviderOperation> diff = source.buildDiff();
@@ -238,14 +215,14 @@
public void testEntityDiffNoneInsert() {
final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
- final AugmentedEntity source = AugmentedEntity.fromBefore(before);
+ final EntityDelta source = EntityDelta.fromBefore(before);
// Insert a new phone number
final ContentValues phone = new ContentValues();
phone.put(Data.MIMETYPE, Phone.MIMETYPE);
phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
phone.put(Phone.TYPE, Phone.TYPE_WORK);
- source.addEntry(AugmentedValues.fromAfter(phone));
+ source.addEntry(ValuesDelta.fromAfter(phone));
// Assert two operations: insert Data row and enforce version
final ArrayList<ContentProviderOperation> diff = source.buildDiff();
@@ -263,7 +240,7 @@
public void testEntityDiffUpdateInsert() {
final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
- final AugmentedEntity source = AugmentedEntity.fromBefore(before);
+ final EntityDelta source = EntityDelta.fromBefore(before);
// Update parent contact values
source.getValues().put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
@@ -273,7 +250,7 @@
phone.put(Data.MIMETYPE, Phone.MIMETYPE);
phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
phone.put(Phone.TYPE, Phone.TYPE_WORK);
- source.addEntry(AugmentedValues.fromAfter(phone));
+ source.addEntry(ValuesDelta.fromAfter(phone));
// Assert three operations: update Contact, insert Data row, enforce version
final ArrayList<ContentProviderOperation> diff = source.buildDiff();
@@ -296,10 +273,10 @@
public void testEntityDiffNoneUpdate() {
final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
- final AugmentedEntity source = AugmentedEntity.fromBefore(before);
+ final EntityDelta source = EntityDelta.fromBefore(before);
// Update existing phone number
- final AugmentedValues child = source.getEntry(TEST_PHONE_ID);
+ final ValuesDelta child = source.getEntry(TEST_PHONE_ID);
child.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
// Assert two operations: update Data and enforce version
@@ -318,7 +295,7 @@
public void testEntityDiffDelete() {
final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
- final AugmentedEntity source = AugmentedEntity.fromBefore(before);
+ final EntityDelta source = EntityDelta.fromBefore(before);
// Delete entire entity
source.getValues().markDeleted();
@@ -343,8 +320,8 @@
after.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
after.put(RawContacts.SEND_TO_VOICEMAIL, 1);
- final AugmentedValues values = AugmentedValues.fromAfter(after);
- final AugmentedEntity source = new AugmentedEntity(values);
+ final ValuesDelta values = ValuesDelta.fromAfter(after);
+ final EntityDelta source = new EntityDelta(values);
// Assert two operations: delete Contact and enforce version
final ArrayList<ContentProviderOperation> diff = source.buildDiff();
@@ -362,15 +339,15 @@
after.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
after.put(RawContacts.SEND_TO_VOICEMAIL, 1);
- final AugmentedValues values = AugmentedValues.fromAfter(after);
- final AugmentedEntity source = new AugmentedEntity(values);
+ final ValuesDelta values = ValuesDelta.fromAfter(after);
+ final EntityDelta source = new EntityDelta(values);
// Insert a new phone number
final ContentValues phone = new ContentValues();
phone.put(Data.MIMETYPE, Phone.MIMETYPE);
phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
phone.put(Phone.TYPE, Phone.TYPE_WORK);
- source.addEntry(AugmentedValues.fromAfter(phone));
+ source.addEntry(ValuesDelta.fromAfter(phone));
// Assert two operations: delete Contact and enforce version
final ArrayList<ContentProviderOperation> diff = source.buildDiff();
diff --git a/tests/src/com/android/contacts/EntityModifierTests.java b/tests/src/com/android/contacts/EntityModifierTests.java
new file mode 100644
index 0000000..9d41f67
--- /dev/null
+++ b/tests/src/com/android/contacts/EntityModifierTests.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts;
+
+import com.android.contacts.model.EntityDelta;
+import com.android.contacts.model.ContactsSource;
+import com.android.contacts.model.EntityModifier;
+import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.model.ContactsSource.EditType;
+
+import android.content.ContentValues;
+import android.content.Entity;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link EntityModifier} to verify that {@link ContactsSource}
+ * constraints are being enforced correctly.
+ */
+@LargeTest
+public class EntityModifierTests extends AndroidTestCase {
+ public static final String TAG = "EntityModifierTests";
+
+ public EntityModifierTests() {
+ super();
+ }
+
+ @Override
+ public void setUp() {
+ mContext = getContext();
+ }
+
+ /**
+ * Build a {@link ContactsSource} that has various odd constraints for
+ * testing purposes.
+ */
+ protected ContactsSource getSource() {
+ final ContactsSource list = new ContactsSource();
+
+ {
+ // Phone allows maximum 2 home, 1 work, and unlimited other, with
+ // constraint of 5 numbers maximum.
+ DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE, -1, -1, 10, true);
+
+ kind.typeOverallMax = 5;
+ kind.typeColumn = Phone.TYPE;
+ kind.typeList = new ArrayList<EditType>();
+ kind.typeList.add(new EditType(Phone.TYPE_HOME, -1, false, 2));
+ kind.typeList.add(new EditType(Phone.TYPE_WORK, -1, false, 1));
+ kind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, -1, true, -1));
+ kind.typeList.add(new EditType(Phone.TYPE_OTHER, -1));
+
+ list.add(kind);
+ }
+
+ return list;
+ }
+
+ /**
+ * Build an {@link Entity} with the requested set of phone numbers.
+ */
+ protected EntityDelta getEntity() {
+ final ContentValues contact = new ContentValues();
+ final Entity before = new Entity(contact);
+ return EntityDelta.fromBefore(before);
+ }
+
+ /**
+ * Assert this {@link List} contains the given {@link Object}.
+ */
+ protected void assertContains(List<?> list, Object object) {
+ assertTrue("Missing expected value", list.contains(object));
+ }
+
+ /**
+ * Assert this {@link List} does not contain the given {@link Object}.
+ */
+ protected void assertNotContains(List<?> list, Object object) {
+ assertFalse("Contained unexpected value", list.contains(object));
+ }
+
+ /**
+ * Insert various rows to test
+ * {@link EntityModifier#getValidTypes(EntityDelta, DataKind, EditType)}
+ */
+ public void testValidTypes() {
+ // Build a source and pull specific types
+ final ContactsSource source = getSource();
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+ final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK);
+ final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER);
+
+ List<EditType> validTypes;
+
+ // Add first home, first work
+ final EntityDelta state = getEntity();
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+ EntityModifier.insertChild(state, kindPhone, typeWork);
+
+ // Expecting home, other
+ validTypes = EntityModifier.getValidTypes(state, kindPhone, null);
+ assertContains(validTypes, typeHome);
+ assertNotContains(validTypes, typeWork);
+ assertContains(validTypes, typeOther);
+
+ // Add second home
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+
+ // Expecting other
+ validTypes = EntityModifier.getValidTypes(state, kindPhone, null);
+ assertNotContains(validTypes, typeHome);
+ assertNotContains(validTypes, typeWork);
+ assertContains(validTypes, typeOther);
+
+ // Add third and fourth home (invalid, but possible)
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+
+ // Expecting none
+ validTypes = EntityModifier.getValidTypes(state, kindPhone, null);
+ assertNotContains(validTypes, typeHome);
+ assertNotContains(validTypes, typeWork);
+ assertNotContains(validTypes, typeOther);
+ }
+
+ /**
+ * Test {@link EntityModifier#canInsert(EntityDelta, DataKind)} by
+ * inserting various rows.
+ */
+ public void testCanInsert() {
+ // Build a source and pull specific types
+ final ContactsSource source = getSource();
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+ final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK);
+ final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER);
+
+ // Add first home, first work
+ final EntityDelta state = getEntity();
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+ EntityModifier.insertChild(state, kindPhone, typeWork);
+ assertTrue("Unable to insert", EntityModifier.canInsert(state, kindPhone));
+
+ // Add two other, which puts us just under "5" overall limit
+ EntityModifier.insertChild(state, kindPhone, typeOther);
+ EntityModifier.insertChild(state, kindPhone, typeOther);
+ assertTrue("Unable to insert", EntityModifier.canInsert(state, kindPhone));
+
+ // Add second home, which should push to snug limit
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+ assertFalse("Able to insert", EntityModifier.canInsert(state, kindPhone));
+ }
+
+ /**
+ * Test {@link EntityModifier#getBestValidType(EntityDelta, DataKind)}
+ * by asserting expected best options in various states.
+ */
+ public void testBestValidType() {
+ // Build a source and pull specific types
+ final ContactsSource source = getSource();
+ final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
+ final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+ final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK);
+ final EditType typeFaxWork = EntityModifier.getType(kindPhone, Phone.TYPE_FAX_WORK);
+ final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER);
+
+ EditType suggested;
+
+ // Default suggestion should be home
+ final EntityDelta state = getEntity();
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false);
+ assertEquals("Unexpected suggestion", typeHome, suggested);
+
+ // Add first home, should now suggest work
+ EntityModifier.insertChild(state, kindPhone, typeHome);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false);
+ assertEquals("Unexpected suggestion", typeWork, suggested);
+
+ // Add work fax, should still suggest work
+ EntityModifier.insertChild(state, kindPhone, typeFaxWork);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false);
+ assertEquals("Unexpected suggestion", typeWork, suggested);
+
+ // Add other, should still suggest work
+ EntityModifier.insertChild(state, kindPhone, typeOther);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false);
+ assertEquals("Unexpected suggestion", typeWork, suggested);
+
+ // Add work, now should suggest other
+ EntityModifier.insertChild(state, kindPhone, typeWork);
+ suggested = EntityModifier.getBestValidType(state, kindPhone, false);
+ assertEquals("Unexpected suggestion", typeOther, suggested);
+ }
+}