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