1863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton/*
2863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * Copyright (C) 2009 The Android Open Source Project
3863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton *
4863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * Licensed under the Apache License, Version 2.0 (the "License");
5863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * you may not use this file except in compliance with the License.
6863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * You may obtain a copy of the License at
7863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton *
8863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton *      http://www.apache.org/licenses/LICENSE-2.0
9863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton *
10863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * Unless required by applicable law or agreed to in writing, software
11863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * distributed under the License is distributed on an "AS IS" BASIS,
12863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * See the License for the specific language governing permissions and
14863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * limitations under the License.
15863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton */
16863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
17863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonpackage com.android.loaderapp.model;
18863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
19863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport com.google.android.collect.Lists;
20863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport com.google.android.collect.Maps;
21863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport com.google.android.collect.Sets;
22863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
23863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport android.content.ContentProviderOperation;
24863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport android.content.ContentValues;
25863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport android.content.Entity;
26863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport android.content.ContentProviderOperation.Builder;
27863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport android.content.Entity.NamedContentValues;
28863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport android.net.Uri;
29863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport android.os.Parcel;
30863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport android.os.Parcelable;
31863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport android.provider.BaseColumns;
32863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport android.provider.ContactsContract.Data;
33863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport android.provider.ContactsContract.RawContacts;
34863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport android.provider.ContactsContract.CommonDataKinds.GroupMembership;
35863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport android.util.Log;
36863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
37863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport java.util.ArrayList;
38863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport java.util.HashMap;
39863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport java.util.HashSet;
40863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport java.util.Map;
41863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonimport java.util.Set;
42863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
43863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton/**
44863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * Contains an {@link Entity} and records any modifications separately so the
45863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * original {@link Entity} can be swapped out with a newer version and the
46863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * changes still cleanly applied.
47863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * <p>
48863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * One benefit of this approach is that we can build changes entirely on an
49863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * empty {@link Entity}, which then becomes an insert {@link RawContacts} case.
50863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * <p>
51863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * When applying modifications over an {@link Entity}, we try finding the
52863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * original {@link Data#_ID} rows where the modifications took place. If those
53863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * rows are missing from the new {@link Entity}, we know the original data must
54863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton * be deleted, but to preserve the user modifications we treat as an insert.
55863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton */
56863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamiltonpublic class EntityDelta implements Parcelable {
57863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    // TODO: optimize by using contentvalues pool, since we allocate so many of them
58863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
59863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    private static final String TAG = "EntityDelta";
60863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    private static final boolean LOGV = true;
61863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
62863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
63863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Direct values from {@link Entity#getEntityValues()}.
64863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
65863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    private ValuesDelta mValues;
66863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
67863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
68863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Internal map of children values from {@link Entity#getSubValues()}, which
69863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * we store here sorted into {@link Data#MIMETYPE} bins.
70863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
71863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    private HashMap<String, ArrayList<ValuesDelta>> mEntries = Maps.newHashMap();
72863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
73863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public EntityDelta() {
74863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
75863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
76863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public EntityDelta(ValuesDelta values) {
77863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        mValues = values;
78863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
79863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
80863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
81863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Build an {@link EntityDelta} using the given {@link Entity} as a
82863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * starting point; the "before" snapshot.
83863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
84863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public static EntityDelta fromBefore(Entity before) {
85863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final EntityDelta entity = new EntityDelta();
86863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        entity.mValues = ValuesDelta.fromBefore(before.getEntityValues());
87863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        entity.mValues.setIdColumn(RawContacts._ID);
88863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        for (NamedContentValues namedValues : before.getSubValues()) {
89863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            entity.addEntry(ValuesDelta.fromBefore(namedValues.values));
90863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
91863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return entity;
92863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
93863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
94863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
95863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Merge the "after" values from the given {@link EntityDelta} onto the
96863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * "before" state represented by this {@link EntityDelta}, discarding any
97863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * existing "after" states. This is typically used when re-parenting changes
98863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * onto an updated {@link Entity}.
99863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
100863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public static EntityDelta mergeAfter(EntityDelta local, EntityDelta remote) {
101863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        // Bail early if trying to merge delete with missing local
102863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final ValuesDelta remoteValues = remote.mValues;
103863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (local == null && (remoteValues.isDelete() || remoteValues.isTransient())) return null;
104863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
105863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        // Create local version if none exists yet
106863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (local == null) local = new EntityDelta();
107863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
108863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (LOGV) {
109863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final Long localVersion = (local.mValues == null) ? null : local.mValues
110863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    .getAsLong(RawContacts.VERSION);
111863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final Long remoteVersion = remote.mValues.getAsLong(RawContacts.VERSION);
112863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            Log.d(TAG, "Re-parenting from original version " + remoteVersion + " to "
113863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    + localVersion);
114863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
115863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
116863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        // Create values if needed, and merge "after" changes
117863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        local.mValues = ValuesDelta.mergeAfter(local.mValues, remote.mValues);
118863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
119863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        // Find matching local entry for each remote values, or create
120863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        for (ArrayList<ValuesDelta> mimeEntries : remote.mEntries.values()) {
121863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            for (ValuesDelta remoteEntry : mimeEntries) {
122863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                final Long childId = remoteEntry.getId();
123863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
124863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                // Find or create local match and merge
125863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                final ValuesDelta localEntry = local.getEntry(childId);
126863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                final ValuesDelta merged = ValuesDelta.mergeAfter(localEntry, remoteEntry);
127863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
128863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                if (localEntry == null && merged != null) {
129863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    // No local entry before, so insert
130863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    local.addEntry(merged);
131863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                }
132863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
133863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
134863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
135863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return local;
136863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
137863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
138863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public ValuesDelta getValues() {
139863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return mValues;
140863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
141863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
142863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public boolean isContactInsert() {
143863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return mValues.isInsert();
144863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
145863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
146863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
147863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Get the {@link ValuesDelta} child marked as {@link Data#IS_PRIMARY},
148863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * which may return null when no entry exists.
149863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
150863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public ValuesDelta getPrimaryEntry(String mimeType) {
151863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final ArrayList<ValuesDelta> mimeEntries = getMimeEntries(mimeType, false);
152863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (mimeEntries == null) return null;
153863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
154863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        for (ValuesDelta entry : mimeEntries) {
155863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (entry.isPrimary()) {
156863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return entry;
157863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
158863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
159863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
160863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        // When no direct primary, return something
161863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return mimeEntries.size() > 0 ? mimeEntries.get(0) : null;
162863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
163863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
164863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
165863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * calls {@link #getSuperPrimaryEntry(String, boolean)} with true
166863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * @see #getSuperPrimaryEntry(String, boolean)
167863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
168863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public ValuesDelta getSuperPrimaryEntry(String mimeType) {
169863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return getSuperPrimaryEntry(mimeType, true);
170863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
171863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
172863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
173863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Returns the super-primary entry for the given mime type
174863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * @param forceSelection if true, will try to return some value even if a super-primary
175863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     *     doesn't exist (may be a primary, or just a random item
176863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * @return
177863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
178863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public ValuesDelta getSuperPrimaryEntry(String mimeType, boolean forceSelection) {
179863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final ArrayList<ValuesDelta> mimeEntries = getMimeEntries(mimeType, false);
180863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (mimeEntries == null) return null;
181863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
182863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        ValuesDelta primary = null;
183863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        for (ValuesDelta entry : mimeEntries) {
184863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (entry.isSuperPrimary()) {
185863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return entry;
186863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            } else if (entry.isPrimary()) {
187863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                primary = entry;
188863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
189863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
190863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
191863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (!forceSelection) {
192863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return null;
193863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
194863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
195863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        // When no direct super primary, return something
196863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (primary != null) {
197863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return primary;
198863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
199863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return mimeEntries.size() > 0 ? mimeEntries.get(0) : null;
200863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
201863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
202863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
203863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Return the list of child {@link ValuesDelta} from our optimized map,
204863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * creating the list if requested.
205863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
206863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    private ArrayList<ValuesDelta> getMimeEntries(String mimeType, boolean lazyCreate) {
207863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        ArrayList<ValuesDelta> mimeEntries = mEntries.get(mimeType);
208863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (mimeEntries == null && lazyCreate) {
209863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            mimeEntries = Lists.newArrayList();
210863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            mEntries.put(mimeType, mimeEntries);
211863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
212863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return mimeEntries;
213863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
214863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
215863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public ArrayList<ValuesDelta> getMimeEntries(String mimeType) {
216863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return getMimeEntries(mimeType, false);
217863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
218863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
219863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public int getMimeEntriesCount(String mimeType, boolean onlyVisible) {
220863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final ArrayList<ValuesDelta> mimeEntries = getMimeEntries(mimeType);
221863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (mimeEntries == null) return 0;
222863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
223863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        int count = 0;
224863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        for (ValuesDelta child : mimeEntries) {
225863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // Skip deleted items when requesting only visible
226863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (onlyVisible && !child.isVisible()) continue;
227863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            count++;
228863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
229863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return count;
230863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
231863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
232863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public boolean hasMimeEntries(String mimeType) {
233863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return mEntries.containsKey(mimeType);
234863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
235863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
236863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public ValuesDelta addEntry(ValuesDelta entry) {
237863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final String mimeType = entry.getMimetype();
238863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        getMimeEntries(mimeType, true).add(entry);
239863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return entry;
240863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
241863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
242863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
243863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Find entry with the given {@link BaseColumns#_ID} value.
244863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
245863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public ValuesDelta getEntry(Long childId) {
246863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (childId == null) {
247863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // Requesting an "insert" entry, which has no "before"
248863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return null;
249863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
250863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
251863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        // Search all children for requested entry
252863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
253863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            for (ValuesDelta entry : mimeEntries) {
254863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                if (childId.equals(entry.getId())) {
255863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    return entry;
256863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                }
257863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
258863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
259863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return null;
260863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
261863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
262863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
263863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Return the total number of {@link ValuesDelta} contained.
264863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
265863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public int getEntryCount(boolean onlyVisible) {
266863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        int count = 0;
267863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        for (String mimeType : mEntries.keySet()) {
268863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            count += getMimeEntriesCount(mimeType, onlyVisible);
269863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
270863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return count;
271863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
272863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
273863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    @Override
274863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public boolean equals(Object object) {
275863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (object instanceof EntityDelta) {
276863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final EntityDelta other = (EntityDelta)object;
277863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
278863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // Equality failed if parent values different
279863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (!other.mValues.equals(mValues)) return false;
280863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
281863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
282863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                for (ValuesDelta child : mimeEntries) {
283863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    // Equality failed if any children unmatched
284863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    if (!other.containsEntry(child)) return false;
285863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                }
286863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
287863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
288863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // Passed all tests, so equal
289863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return true;
290863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
291863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return false;
292863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
293863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
294863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    private boolean containsEntry(ValuesDelta entry) {
295863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
296863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            for (ValuesDelta child : mimeEntries) {
297863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                // Contained if we find any child that matches
298863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                if (child.equals(entry)) return true;
299863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
300863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
301863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return false;
302863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
303863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
304863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
305863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Mark this entire object deleted, including any {@link ValuesDelta}.
306863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
307863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public void markDeleted() {
308863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        this.mValues.markDeleted();
309863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
310863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            for (ValuesDelta child : mimeEntries) {
311863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                child.markDeleted();
312863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
313863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
314863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
315863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
316863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    @Override
317863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public String toString() {
318863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final StringBuilder builder = new StringBuilder();
319863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        builder.append("\n(");
320863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        builder.append(mValues.toString());
321863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        builder.append(") = {");
322863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
323863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            for (ValuesDelta child : mimeEntries) {
324863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                builder.append("\n\t");
325863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                child.toString(builder);
326863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
327863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
328863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        builder.append("\n}\n");
329863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return builder.toString();
330863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
331863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
332863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
333863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Consider building the given {@link ContentProviderOperation.Builder} and
334863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * appending it to the given list, which only happens if builder is valid.
335863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
336863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    private void possibleAdd(ArrayList<ContentProviderOperation> diff,
337863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            ContentProviderOperation.Builder builder) {
338863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (builder != null) {
339863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            diff.add(builder.build());
340863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
341863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
342863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
343863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
344863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Build a list of {@link ContentProviderOperation} that will assert any
345863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * "before" state hasn't changed. This is maintained separately so that all
346863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * asserts can take place before any updates occur.
347863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
348863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public void buildAssert(ArrayList<ContentProviderOperation> buildInto) {
349863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final boolean isContactInsert = mValues.isInsert();
350863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (!isContactInsert) {
351863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // Assert version is consistent while persisting changes
352863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final Long beforeId = mValues.getId();
353863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final Long beforeVersion = mValues.getAsLong(RawContacts.VERSION);
354863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (beforeId == null || beforeVersion == null) return;
355863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
356863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final ContentProviderOperation.Builder builder = ContentProviderOperation
357863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    .newAssertQuery(RawContacts.CONTENT_URI);
358863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            builder.withSelection(RawContacts._ID + "=" + beforeId, null);
359863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            builder.withValue(RawContacts.VERSION, beforeVersion);
360863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            buildInto.add(builder.build());
361863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
362863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
363863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
364863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
365863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Build a list of {@link ContentProviderOperation} that will transform the
366863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * current "before" {@link Entity} state into the modified state which this
367863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * {@link EntityDelta} represents.
368863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
369863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public void buildDiff(ArrayList<ContentProviderOperation> buildInto) {
370863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final int firstIndex = buildInto.size();
371863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
372863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final boolean isContactInsert = mValues.isInsert();
373863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final boolean isContactDelete = mValues.isDelete();
374863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final boolean isContactUpdate = !isContactInsert && !isContactDelete;
375863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
376863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final Long beforeId = mValues.getId();
377863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
378863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        Builder builder;
379863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
380863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (isContactInsert) {
381863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // TODO: for now simply disabling aggregation when a new contact is
382863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // created on the phone.  In the future, will show aggregation suggestions
383863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // after saving the contact.
384863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            mValues.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
385863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
386863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
387863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        // Build possible operation at Contact level
388863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        builder = mValues.buildDiff(RawContacts.CONTENT_URI);
389863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        possibleAdd(buildInto, builder);
390863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
391863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        // Build operations for all children
392863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
393863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            for (ValuesDelta child : mimeEntries) {
394863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                // Ignore children if parent was deleted
395863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                if (isContactDelete) continue;
396863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
397863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                builder = child.buildDiff(Data.CONTENT_URI);
398863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                if (child.isInsert()) {
399863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    if (isContactInsert) {
400863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                        // Parent is brand new insert, so back-reference _id
401863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                        builder.withValueBackReference(Data.RAW_CONTACT_ID, firstIndex);
402863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    } else {
403863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                        // Inserting under existing, so fill with known _id
404863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                        builder.withValue(Data.RAW_CONTACT_ID, beforeId);
405863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    }
406863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                } else if (isContactInsert && builder != null) {
407863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    // Child must be insert when Contact insert
408863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    throw new IllegalArgumentException("When parent insert, child must be also");
409863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                }
410863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                possibleAdd(buildInto, builder);
411863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
412863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
413863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
414863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final boolean addedOperations = buildInto.size() > firstIndex;
415863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        if (addedOperations && isContactUpdate) {
416863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // Suspend aggregation while persisting updates
417863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            builder = buildSetAggregationMode(beforeId, RawContacts.AGGREGATION_MODE_SUSPENDED);
418863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            buildInto.add(firstIndex, builder.build());
419863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
420863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // Restore aggregation mode as last operation
421863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            builder = buildSetAggregationMode(beforeId, RawContacts.AGGREGATION_MODE_DEFAULT);
422863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            buildInto.add(builder.build());
423863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        } else if (isContactInsert) {
424863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // Restore aggregation mode as last operation
425863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            builder = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI);
426863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT);
427863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            builder.withSelection(RawContacts._ID + "=?", new String[1]);
428863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            builder.withSelectionBackReference(0, firstIndex);
429863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            buildInto.add(builder.build());
430863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
431863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
432863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
433863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
434863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Build a {@link ContentProviderOperation} that changes
435863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * {@link RawContacts#AGGREGATION_MODE} to the given value.
436863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
437863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    protected Builder buildSetAggregationMode(Long beforeId, int mode) {
438863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        Builder builder = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI);
439863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        builder.withValue(RawContacts.AGGREGATION_MODE, mode);
440863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        builder.withSelection(RawContacts._ID + "=" + beforeId, null);
441863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return builder;
442863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
443863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
444863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /** {@inheritDoc} */
445863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public int describeContents() {
446863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        // Nothing special about this parcel
447863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        return 0;
448863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
449863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
450863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /** {@inheritDoc} */
451863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public void writeToParcel(Parcel dest, int flags) {
452863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final int size = this.getEntryCount(false);
453863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        dest.writeInt(size);
454863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        dest.writeParcelable(mValues, flags);
455863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        for (ArrayList<ValuesDelta> mimeEntries : mEntries.values()) {
456863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            for (ValuesDelta child : mimeEntries) {
457863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                dest.writeParcelable(child, flags);
458863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
459863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
460863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
461863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
462863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public void readFromParcel(Parcel source) {
463863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final ClassLoader loader = getClass().getClassLoader();
464863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        final int size = source.readInt();
465863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        mValues = source.<ValuesDelta> readParcelable(loader);
466863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        for (int i = 0; i < size; i++) {
467863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final ValuesDelta child = source.<ValuesDelta> readParcelable(loader);
468863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            this.addEntry(child);
469863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
470863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
471863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
472863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public static final Parcelable.Creator<EntityDelta> CREATOR = new Parcelable.Creator<EntityDelta>() {
473863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public EntityDelta createFromParcel(Parcel in) {
474863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final EntityDelta state = new EntityDelta();
475863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            state.readFromParcel(in);
476863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return state;
477863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
478863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
479863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public EntityDelta[] newArray(int size) {
480863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return new EntityDelta[size];
481863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
482863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    };
483863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
484863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    /**
485863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * Type of {@link ContentValues} that maintains both an original state and a
486863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * modified version of that state. This allows us to build insert, update,
487863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     * or delete operations based on a "before" {@link Entity} snapshot.
488863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton     */
489863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    public static class ValuesDelta implements Parcelable {
490863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        protected ContentValues mBefore;
491863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        protected ContentValues mAfter;
492863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        protected String mIdColumn = BaseColumns._ID;
493863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        private boolean mFromTemplate;
494863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
495863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        /**
496863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * Next value to assign to {@link #mIdColumn} when building an insert
497863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * operation through {@link #fromAfter(ContentValues)}. This is used so
498863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * we can concretely reference this {@link ValuesDelta} before it has
499863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * been persisted.
500863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         */
501863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        protected static int sNextInsertId = -1;
502863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
503863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        protected ValuesDelta() {
504863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
505863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
506863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        /**
507863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * Create {@link ValuesDelta}, using the given object as the
508863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * "before" state, usually from an {@link Entity}.
509863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         */
510863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public static ValuesDelta fromBefore(ContentValues before) {
511863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final ValuesDelta entry = new ValuesDelta();
512863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            entry.mBefore = before;
513863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            entry.mAfter = new ContentValues();
514863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return entry;
515863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
516863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
517863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        /**
518863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * Create {@link ValuesDelta}, using the given object as the "after"
519863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * state, usually when we are inserting a row instead of updating.
520863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         */
521863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public static ValuesDelta fromAfter(ContentValues after) {
522863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final ValuesDelta entry = new ValuesDelta();
523863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            entry.mBefore = null;
524863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            entry.mAfter = after;
525863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
526863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // Assign temporary id which is dropped before insert.
527863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            entry.mAfter.put(entry.mIdColumn, sNextInsertId--);
528863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return entry;
529863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
530863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
531863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public ContentValues getAfter() {
532863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return mAfter;
533863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
534863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
535863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public String getAsString(String key) {
536863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (mAfter != null && mAfter.containsKey(key)) {
537863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return mAfter.getAsString(key);
538863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            } else if (mBefore != null && mBefore.containsKey(key)) {
539863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return mBefore.getAsString(key);
540863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            } else {
541863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return null;
542863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
543863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
544863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
545863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public byte[] getAsByteArray(String key) {
546863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (mAfter != null && mAfter.containsKey(key)) {
547863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return mAfter.getAsByteArray(key);
548863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            } else if (mBefore != null && mBefore.containsKey(key)) {
549863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return mBefore.getAsByteArray(key);
550863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            } else {
551863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return null;
552863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
553863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
554863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
555863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public Long getAsLong(String key) {
556863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (mAfter != null && mAfter.containsKey(key)) {
557863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return mAfter.getAsLong(key);
558863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            } else if (mBefore != null && mBefore.containsKey(key)) {
559863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return mBefore.getAsLong(key);
560863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            } else {
561863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return null;
562863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
563863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
564863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
565863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public Integer getAsInteger(String key) {
566863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return getAsInteger(key, null);
567863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
568863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
569863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public Integer getAsInteger(String key, Integer defaultValue) {
570863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (mAfter != null && mAfter.containsKey(key)) {
571863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return mAfter.getAsInteger(key);
572863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            } else if (mBefore != null && mBefore.containsKey(key)) {
573863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return mBefore.getAsInteger(key);
574863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            } else {
575863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return defaultValue;
576863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
577863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
578863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
579863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public String getMimetype() {
580863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return getAsString(Data.MIMETYPE);
581863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
582863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
583863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public Long getId() {
584863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return getAsLong(mIdColumn);
585863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
586863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
587863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public void setIdColumn(String idColumn) {
588863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            mIdColumn = idColumn;
589863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
590863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
591863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public boolean isPrimary() {
592863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final Long isPrimary = getAsLong(Data.IS_PRIMARY);
593863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return isPrimary == null ? false : isPrimary != 0;
594863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
595863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
596863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public void setFromTemplate(boolean isFromTemplate) {
597863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            mFromTemplate = isFromTemplate;
598863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
599863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
600863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public boolean isFromTemplate() {
601863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return mFromTemplate;
602863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
603863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
604863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public boolean isSuperPrimary() {
605863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final Long isSuperPrimary = getAsLong(Data.IS_SUPER_PRIMARY);
606863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return isSuperPrimary == null ? false : isSuperPrimary != 0;
607863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
608863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
609863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public boolean beforeExists() {
610863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return (mBefore != null && mBefore.containsKey(mIdColumn));
611863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
612863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
613863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public boolean isVisible() {
614863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // When "after" is present, then visible
615863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return (mAfter != null);
616863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
617863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
618863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public boolean isDelete() {
619863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // When "after" is wiped, action is "delete"
620863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return beforeExists() && (mAfter == null);
621863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
622863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
623863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public boolean isTransient() {
624863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // When no "before" or "after", is transient
625863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return (mBefore == null) && (mAfter == null);
626863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
627863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
628863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public boolean isUpdate() {
629863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // When "after" has some changes, action is "update"
630863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return beforeExists() && (mAfter != null && mAfter.size() > 0);
631863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
632863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
633863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public boolean isNoop() {
634863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // When "after" has no changes, action is no-op
635863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return beforeExists() && (mAfter != null && mAfter.size() == 0);
636863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
637863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
638863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public boolean isInsert() {
639863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // When no "before" id, and has "after", action is "insert"
640863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return !beforeExists() && (mAfter != null);
641863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
642863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
643863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public void markDeleted() {
644863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            mAfter = null;
645863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
646863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
647863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        /**
648863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * Ensure that our internal structure is ready for storing updates.
649863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         */
650863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        private void ensureUpdate() {
651863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (mAfter == null) {
652863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                mAfter = new ContentValues();
653863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
654863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
655863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
656863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public void put(String key, String value) {
657863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            ensureUpdate();
658863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            mAfter.put(key, value);
659863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
660863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
661863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public void put(String key, byte[] value) {
662863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            ensureUpdate();
663863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            mAfter.put(key, value);
664863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
665863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
666863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public void put(String key, int value) {
667863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            ensureUpdate();
668863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            mAfter.put(key, value);
669863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
670863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
671863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        /**
672863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * Return set of all keys defined through this object.
673863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         */
674863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public Set<String> keySet() {
675863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final HashSet<String> keys = Sets.newHashSet();
676863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
677863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (mBefore != null) {
678863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                for (Map.Entry<String, Object> entry : mBefore.valueSet()) {
679863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    keys.add(entry.getKey());
680863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                }
681863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
682863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
683863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (mAfter != null) {
684863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                for (Map.Entry<String, Object> entry : mAfter.valueSet()) {
685863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    keys.add(entry.getKey());
686863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                }
687863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
688863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
689863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return keys;
690863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
691863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
692863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        /**
693863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * Return complete set of "before" and "after" values mixed together,
694863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * giving full state regardless of edits.
695863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         */
696863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public ContentValues getCompleteValues() {
697863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final ContentValues values = new ContentValues();
698863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (mBefore != null) {
699863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                values.putAll(mBefore);
700863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
701863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (mAfter != null) {
702863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                values.putAll(mAfter);
703863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
704863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (values.containsKey(GroupMembership.GROUP_ROW_ID)) {
705863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                // Clear to avoid double-definitions, and prefer rows
706863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                values.remove(GroupMembership.GROUP_SOURCE_ID);
707863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
708863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
709863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return values;
710863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
711863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
712863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        /**
713863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * Merge the "after" values from the given {@link ValuesDelta},
714863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * discarding any existing "after" state. This is typically used when
715863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * re-parenting changes onto an updated {@link Entity}.
716863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         */
717863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public static ValuesDelta mergeAfter(ValuesDelta local, ValuesDelta remote) {
718863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // Bail early if trying to merge delete with missing local
719863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (local == null && (remote.isDelete() || remote.isTransient())) return null;
720863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
721863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // Create local version if none exists yet
722863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (local == null) local = new ValuesDelta();
723863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
724863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (!local.beforeExists()) {
725863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                // Any "before" record is missing, so take all values as "insert"
726863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                local.mAfter = remote.getCompleteValues();
727863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            } else {
728863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                // Existing "update" with only "after" values
729863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                local.mAfter = remote.mAfter;
730863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
731863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
732863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return local;
733863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
734863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
735863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        @Override
736863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public boolean equals(Object object) {
737863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (object instanceof ValuesDelta) {
738863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                // Only exactly equal with both are identical subsets
739863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                final ValuesDelta other = (ValuesDelta)object;
740863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return this.subsetEquals(other) && other.subsetEquals(this);
741863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
742863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return false;
743863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
744863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
745863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        @Override
746863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public String toString() {
747863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final StringBuilder builder = new StringBuilder();
748863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            toString(builder);
749863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return builder.toString();
750863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
751863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
752863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        /**
753863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * Helper for building string representation, leveraging the given
754863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * {@link StringBuilder} to minimize allocations.
755863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         */
756863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public void toString(StringBuilder builder) {
757863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            builder.append("{ ");
758863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            for (String key : this.keySet()) {
759863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                builder.append(key);
760863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                builder.append("=");
761863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                builder.append(this.getAsString(key));
762863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                builder.append(", ");
763863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
764863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            builder.append("}");
765863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
766863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
767863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        /**
768863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * Check if the given {@link ValuesDelta} is both a subset of this
769863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * object, and any defined keys have equal values.
770863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         */
771863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public boolean subsetEquals(ValuesDelta other) {
772863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            for (String key : this.keySet()) {
773863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                final String ourValue = this.getAsString(key);
774863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                final String theirValue = other.getAsString(key);
775863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                if (ourValue == null) {
776863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    // If they have value when we're null, no match
777863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    if (theirValue != null) return false;
778863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                } else {
779863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    // If both values defined and aren't equal, no match
780863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                    if (!ourValue.equals(theirValue)) return false;
781863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                }
782863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
783863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // All values compared and matched
784863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return true;
785863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
786863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
787863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        /**
788863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * Build a {@link ContentProviderOperation} that will transform our
789863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * "before" state into our "after" state, using insert, update, or
790863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         * delete as needed.
791863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton         */
792863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public ContentProviderOperation.Builder buildDiff(Uri targetUri) {
793863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            Builder builder = null;
794863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            if (isInsert()) {
795863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                // Changed values are "insert" back-referenced to Contact
796863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                mAfter.remove(mIdColumn);
797863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                builder = ContentProviderOperation.newInsert(targetUri);
798863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                builder.withValues(mAfter);
799863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            } else if (isDelete()) {
800863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                // When marked for deletion and "before" exists, then "delete"
801863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                builder = ContentProviderOperation.newDelete(targetUri);
802863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                builder.withSelection(mIdColumn + "=" + getId(), null);
803863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            } else if (isUpdate()) {
804863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                // When has changes and "before" exists, then "update"
805863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                builder = ContentProviderOperation.newUpdate(targetUri);
806863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                builder.withSelection(mIdColumn + "=" + getId(), null);
807863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                builder.withValues(mAfter);
808863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
809863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return builder;
810863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
811863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
812863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        /** {@inheritDoc} */
813863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public int describeContents() {
814863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            // Nothing special about this parcel
815863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            return 0;
816863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
817863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
818863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        /** {@inheritDoc} */
819863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public void writeToParcel(Parcel dest, int flags) {
820863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            dest.writeParcelable(mBefore, flags);
821863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            dest.writeParcelable(mAfter, flags);
822863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            dest.writeString(mIdColumn);
823863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
824863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
825863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public void readFromParcel(Parcel source) {
826863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            final ClassLoader loader = getClass().getClassLoader();
827863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            mBefore = source.<ContentValues> readParcelable(loader);
828863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            mAfter = source.<ContentValues> readParcelable(loader);
829863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            mIdColumn = source.readString();
830863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        }
831863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
832863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        public static final Parcelable.Creator<ValuesDelta> CREATOR = new Parcelable.Creator<ValuesDelta>() {
833863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            public ValuesDelta createFromParcel(Parcel in) {
834863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                final ValuesDelta values = new ValuesDelta();
835863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                values.readFromParcel(in);
836863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return values;
837863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
838863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton
839863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            public ValuesDelta[] newArray(int size) {
840863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton                return new ValuesDelta[size];
841863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton            }
842863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton        };
843863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton    }
844863e7a55dc45cd1210e4d07e5847f48dfe301876Jeff Hamilton}
845