/** * Build a list of {@link ContentProviderOperation} that will transform all the "before" {@link * Entity} states into the modified state which all {@link EntityDelta} objects represent. This * method specifically creates any {@link AggregationExceptions} rules needed to groups edits * together. */ public ArrayList<ContentProviderOperation> buildDiff() { final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); final long rawContactId = this.findRawContactId(); int firstInsertRow = -1; // First pass enforces versions remain consistent for (EntityDelta delta : this) { delta.buildAssert(diff); } final int assertMark = diff.size(); int backRefs[] = new int[size()]; int rawContactIndex = 0; // Second pass builds actual operations for (EntityDelta delta : this) { final int firstBatch = diff.size(); backRefs[rawContactIndex++] = firstBatch; delta.buildDiff(diff); // Only create rules for inserts if (!delta.isContactInsert()) continue; // If we are going to split all contacts, there is no point in first combining them if (mSplitRawContacts) continue; if (rawContactId != -1) { // Has existing contact, so bind to it strongly final Builder builder = beginKeepTogether(); builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId); builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch); diff.add(builder.build()); } else if (firstInsertRow == -1) { // First insert case, so record row firstInsertRow = firstBatch; } else { // Additional insert case, so point at first insert final Builder builder = beginKeepTogether(); builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1, firstInsertRow); builder.withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, firstBatch); diff.add(builder.build()); } } if (mSplitRawContacts) { buildSplitContactDiff(diff, backRefs); } // No real changes if only left with asserts if (diff.size() == assertMark) { diff.clear(); } return diff; }
/** * Search all contained {@link EntityDelta} for the first one with an existing {@link * RawContacts#_ID} value. Usually used when creating {@link AggregationExceptions} during an * update. */ public long findRawContactId() { for (EntityDelta delta : this) { final Long rawContactId = delta.getValues().getAsLong(RawContacts._ID); if (rawContactId != null && rawContactId >= 0) { return rawContactId; } } return -1; }
/** Find {@link RawContacts#_ID} of the requested {@link EntityDelta}. */ public Long getRawContactId(int index) { if (index >= 0 && index < this.size()) { final EntityDelta delta = this.get(index); final ValuesDelta values = delta.getValues(); if (values.isVisible()) { return values.getAsLong(RawContacts._ID); } } return null; }
/** * Merge the "after" values from the given {@link EntitySet}, discarding any previous "after" * states. This is typically used when re-parenting user edits onto an updated {@link EntitySet}. */ public static EntitySet mergeAfter(EntitySet local, EntitySet remote) { if (local == null) local = new EntitySet(); // For each entity in the remote set, try matching over existing for (EntityDelta remoteEntity : remote) { final Long rawContactId = remoteEntity.getValues().getId(); // Find or create local match and merge final EntityDelta localEntity = local.getByRawContactId(rawContactId); final EntityDelta merged = EntityDelta.mergeAfter(localEntity, remoteEntity); if (localEntity == null && merged != null) { // No local entry before, so insert local.add(merged); } } return local; }
public ValuesDelta getSuperPrimaryEntry(final String mimeType) { ValuesDelta primary = null; ValuesDelta randomEntry = null; for (EntityDelta delta : this) { final ArrayList<ValuesDelta> mimeEntries = delta.getMimeEntries(mimeType); if (mimeEntries == null) return null; for (ValuesDelta entry : mimeEntries) { if (entry.isSuperPrimary()) { return entry; } else if (primary == null && entry.isPrimary()) { primary = entry; } else if (randomEntry == null) { randomEntry = entry; } } } // When no direct super primary, return something if (primary != null) { return primary; } return randomEntry; }
/** * Create an {@link EntitySet} based on {@link Contacts} specified by the given query parameters. * This closes the {@link EntityIterator} when finished, so it doesn't subscribe to updates. */ public static EntitySet fromQuery( ContentResolver resolver, String selection, String[] selectionArgs, String sortOrder) { EntityIterator iterator = null; final EntitySet state = new EntitySet(); try { // Perform background query to pull contact details iterator = resolver.queryEntities(RawContacts.CONTENT_URI, selection, selectionArgs, sortOrder); while (iterator.hasNext()) { // Read all contacts into local deltas to prepare for edits final Entity before = iterator.next(); final EntityDelta entity = EntityDelta.fromBefore(before); state.add(entity); } } catch (RemoteException e) { throw new IllegalStateException("Problem querying contact details", e); } finally { if (iterator != null) { iterator.close(); } } return state; }