1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.contacts.common.model;
18 
19 import android.content.ContentProviderOperation;
20 import android.content.ContentValues;
21 import android.net.Uri;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.provider.BaseColumns;
25 import android.provider.ContactsContract;
26 
27 import com.android.contacts.common.testing.NeededForTesting;
28 import com.google.common.collect.Sets;
29 
30 import java.util.HashSet;
31 import java.util.Map;
32 import java.util.Set;
33 
34 /**
35  * Type of {@link android.content.ContentValues} that maintains both an original state and a
36  * modified version of that state. This allows us to build insert, update,
37  * or delete operations based on a "before" {@link Entity} snapshot.
38  */
39 public class ValuesDelta implements Parcelable {
40     protected ContentValues mBefore;
41     protected ContentValues mAfter;
42     protected String mIdColumn = BaseColumns._ID;
43     private boolean mFromTemplate;
44 
45     /**
46      * Next value to assign to {@link #mIdColumn} when building an insert
47      * operation through {@link #fromAfter(android.content.ContentValues)}. This is used so
48      * we can concretely reference this {@link ValuesDelta} before it has
49      * been persisted.
50      */
51     protected static int sNextInsertId = -1;
52 
ValuesDelta()53     protected ValuesDelta() {
54     }
55 
56     /**
57      * Create {@link ValuesDelta}, using the given object as the
58      * "before" state, usually from an {@link Entity}.
59      */
fromBefore(ContentValues before)60     public static ValuesDelta fromBefore(ContentValues before) {
61         final ValuesDelta entry = new ValuesDelta();
62         entry.mBefore = before;
63         entry.mAfter = new ContentValues();
64         return entry;
65     }
66 
67     /**
68      * Create {@link ValuesDelta}, using the given object as the "after"
69      * state, usually when we are inserting a row instead of updating.
70      */
fromAfter(ContentValues after)71     public static ValuesDelta fromAfter(ContentValues after) {
72         final ValuesDelta entry = new ValuesDelta();
73         entry.mBefore = null;
74         entry.mAfter = after;
75 
76         // Assign temporary id which is dropped before insert.
77         entry.mAfter.put(entry.mIdColumn, sNextInsertId--);
78         return entry;
79     }
80 
81     @NeededForTesting
getAfter()82     public ContentValues getAfter() {
83         return mAfter;
84     }
85 
containsKey(String key)86     public boolean containsKey(String key) {
87         return ((mAfter != null && mAfter.containsKey(key)) ||
88                 (mBefore != null && mBefore.containsKey(key)));
89     }
90 
getAsString(String key)91     public String getAsString(String key) {
92         if (mAfter != null && mAfter.containsKey(key)) {
93             return mAfter.getAsString(key);
94         } else if (mBefore != null && mBefore.containsKey(key)) {
95             return mBefore.getAsString(key);
96         } else {
97             return null;
98         }
99     }
100 
getAsByteArray(String key)101     public byte[] getAsByteArray(String key) {
102         if (mAfter != null && mAfter.containsKey(key)) {
103             return mAfter.getAsByteArray(key);
104         } else if (mBefore != null && mBefore.containsKey(key)) {
105             return mBefore.getAsByteArray(key);
106         } else {
107             return null;
108         }
109     }
110 
getAsLong(String key)111     public Long getAsLong(String key) {
112         if (mAfter != null && mAfter.containsKey(key)) {
113             return mAfter.getAsLong(key);
114         } else if (mBefore != null && mBefore.containsKey(key)) {
115             return mBefore.getAsLong(key);
116         } else {
117             return null;
118         }
119     }
120 
getAsInteger(String key)121     public Integer getAsInteger(String key) {
122         return getAsInteger(key, null);
123     }
124 
getAsInteger(String key, Integer defaultValue)125     public Integer getAsInteger(String key, Integer defaultValue) {
126         if (mAfter != null && mAfter.containsKey(key)) {
127             return mAfter.getAsInteger(key);
128         } else if (mBefore != null && mBefore.containsKey(key)) {
129             return mBefore.getAsInteger(key);
130         } else {
131             return defaultValue;
132         }
133     }
134 
isChanged(String key)135     public boolean isChanged(String key) {
136         if (mAfter == null || !mAfter.containsKey(key)) {
137             return false;
138         }
139 
140         Object newValue = mAfter.get(key);
141         Object oldValue = mBefore.get(key);
142 
143         if (oldValue == null) {
144             return newValue != null;
145         }
146 
147         return !oldValue.equals(newValue);
148     }
149 
getMimetype()150     public String getMimetype() {
151         return getAsString(ContactsContract.Data.MIMETYPE);
152     }
153 
getId()154     public Long getId() {
155         return getAsLong(mIdColumn);
156     }
157 
setIdColumn(String idColumn)158     public void setIdColumn(String idColumn) {
159         mIdColumn = idColumn;
160     }
161 
isPrimary()162     public boolean isPrimary() {
163         final Long isPrimary = getAsLong(ContactsContract.Data.IS_PRIMARY);
164         return isPrimary == null ? false : isPrimary != 0;
165     }
166 
setFromTemplate(boolean isFromTemplate)167     public void setFromTemplate(boolean isFromTemplate) {
168         mFromTemplate = isFromTemplate;
169     }
170 
isFromTemplate()171     public boolean isFromTemplate() {
172         return mFromTemplate;
173     }
174 
isSuperPrimary()175     public boolean isSuperPrimary() {
176         final Long isSuperPrimary = getAsLong(ContactsContract.Data.IS_SUPER_PRIMARY);
177         return isSuperPrimary == null ? false : isSuperPrimary != 0;
178     }
179 
beforeExists()180     public boolean beforeExists() {
181         return (mBefore != null && mBefore.containsKey(mIdColumn));
182     }
183 
184     /**
185      * When "after" is present, then visible
186      */
isVisible()187     public boolean isVisible() {
188         return (mAfter != null);
189     }
190 
191     /**
192      * When "after" is wiped, action is "delete"
193      */
isDelete()194     public boolean isDelete() {
195         return beforeExists() && (mAfter == null);
196     }
197 
198     /**
199      * When no "before" or "after", is transient
200      */
isTransient()201     public boolean isTransient() {
202         return (mBefore == null) && (mAfter == null);
203     }
204 
205     /**
206      * When "after" has some changes, action is "update"
207      */
isUpdate()208     public boolean isUpdate() {
209         if (!beforeExists() || mAfter == null || mAfter.size() == 0) {
210             return false;
211         }
212         for (String key : mAfter.keySet()) {
213             Object newValue = mAfter.get(key);
214             Object oldValue = mBefore.get(key);
215             if (oldValue == null) {
216                 if (newValue != null) {
217                     return true;
218                 }
219             } else if (!oldValue.equals(newValue)) {
220                 return true;
221             }
222         }
223         return false;
224     }
225 
226     /**
227      * When "after" has no changes, action is no-op
228      */
isNoop()229     public boolean isNoop() {
230         return beforeExists() && (mAfter != null && mAfter.size() == 0);
231     }
232 
233     /**
234      * When no "before" id, and has "after", action is "insert"
235      */
isInsert()236     public boolean isInsert() {
237         return !beforeExists() && (mAfter != null);
238     }
239 
markDeleted()240     public void markDeleted() {
241         mAfter = null;
242     }
243 
244     /**
245      * Ensure that our internal structure is ready for storing updates.
246      */
ensureUpdate()247     private void ensureUpdate() {
248         if (mAfter == null) {
249             mAfter = new ContentValues();
250         }
251     }
252 
put(String key, String value)253     public void put(String key, String value) {
254         ensureUpdate();
255         mAfter.put(key, value);
256     }
257 
put(String key, byte[] value)258     public void put(String key, byte[] value) {
259         ensureUpdate();
260         mAfter.put(key, value);
261     }
262 
put(String key, int value)263     public void put(String key, int value) {
264         ensureUpdate();
265         mAfter.put(key, value);
266     }
267 
put(String key, long value)268     public void put(String key, long value) {
269         ensureUpdate();
270         mAfter.put(key, value);
271     }
272 
putNull(String key)273     public void putNull(String key) {
274         ensureUpdate();
275         mAfter.putNull(key);
276     }
277 
copyStringFrom(ValuesDelta from, String key)278     public void copyStringFrom(ValuesDelta from, String key) {
279         ensureUpdate();
280         put(key, from.getAsString(key));
281     }
282 
283     /**
284      * Return set of all keys defined through this object.
285      */
keySet()286     public Set<String> keySet() {
287         final HashSet<String> keys = Sets.newHashSet();
288 
289         if (mBefore != null) {
290             for (Map.Entry<String, Object> entry : mBefore.valueSet()) {
291                 keys.add(entry.getKey());
292             }
293         }
294 
295         if (mAfter != null) {
296             for (Map.Entry<String, Object> entry : mAfter.valueSet()) {
297                 keys.add(entry.getKey());
298             }
299         }
300 
301         return keys;
302     }
303 
304     /**
305      * Return complete set of "before" and "after" values mixed together,
306      * giving full state regardless of edits.
307      */
getCompleteValues()308     public ContentValues getCompleteValues() {
309         final ContentValues values = new ContentValues();
310         if (mBefore != null) {
311             values.putAll(mBefore);
312         }
313         if (mAfter != null) {
314             values.putAll(mAfter);
315         }
316         if (values.containsKey(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID)) {
317             // Clear to avoid double-definitions, and prefer rows
318             values.remove(ContactsContract.CommonDataKinds.GroupMembership.GROUP_SOURCE_ID);
319         }
320 
321         return values;
322     }
323 
324     /**
325      * Merge the "after" values from the given {@link ValuesDelta},
326      * discarding any existing "after" state. This is typically used when
327      * re-parenting changes onto an updated {@link Entity}.
328      */
mergeAfter(ValuesDelta local, ValuesDelta remote)329     public static ValuesDelta mergeAfter(ValuesDelta local, ValuesDelta remote) {
330         // Bail early if trying to merge delete with missing local
331         if (local == null && (remote.isDelete() || remote.isTransient())) return null;
332 
333         // Create local version if none exists yet
334         if (local == null) local = new ValuesDelta();
335 
336         if (!local.beforeExists()) {
337             // Any "before" record is missing, so take all values as "insert"
338             local.mAfter = remote.getCompleteValues();
339         } else {
340             // Existing "update" with only "after" values
341             local.mAfter = remote.mAfter;
342         }
343 
344         return local;
345     }
346 
347     @Override
equals(Object object)348     public boolean equals(Object object) {
349         if (object instanceof ValuesDelta) {
350             // Only exactly equal with both are identical subsets
351             final ValuesDelta other = (ValuesDelta)object;
352             return this.subsetEquals(other) && other.subsetEquals(this);
353         }
354         return false;
355     }
356 
357     @Override
toString()358     public String toString() {
359         final StringBuilder builder = new StringBuilder();
360         toString(builder);
361         return builder.toString();
362     }
363 
364     /**
365      * Helper for building string representation, leveraging the given
366      * {@link StringBuilder} to minimize allocations.
367      */
toString(StringBuilder builder)368     public void toString(StringBuilder builder) {
369         builder.append("{ ");
370         builder.append("IdColumn=");
371         builder.append(mIdColumn);
372         builder.append(", FromTemplate=");
373         builder.append(mFromTemplate);
374         builder.append(", ");
375         for (String key : this.keySet()) {
376             builder.append(key);
377             builder.append("=");
378             builder.append(this.getAsString(key));
379             builder.append(", ");
380         }
381         builder.append("}");
382     }
383 
384     /**
385      * Check if the given {@link ValuesDelta} is both a subset of this
386      * object, and any defined keys have equal values.
387      */
subsetEquals(ValuesDelta other)388     public boolean subsetEquals(ValuesDelta other) {
389         for (String key : this.keySet()) {
390             final String ourValue = this.getAsString(key);
391             final String theirValue = other.getAsString(key);
392             if (ourValue == null) {
393                 // If they have value when we're null, no match
394                 if (theirValue != null) return false;
395             } else {
396                 // If both values defined and aren't equal, no match
397                 if (!ourValue.equals(theirValue)) return false;
398             }
399         }
400         // All values compared and matched
401         return true;
402     }
403 
404     /**
405      * Build a {@link android.content.ContentProviderOperation} that will transform our
406      * "before" state into our "after" state, using insert, update, or
407      * delete as needed.
408      */
buildDiff(Uri targetUri)409     public ContentProviderOperation.Builder buildDiff(Uri targetUri) {
410         ContentProviderOperation.Builder builder = null;
411         if (isInsert()) {
412             // Changed values are "insert" back-referenced to Contact
413             mAfter.remove(mIdColumn);
414             builder = ContentProviderOperation.newInsert(targetUri);
415             builder.withValues(mAfter);
416         } else if (isDelete()) {
417             // When marked for deletion and "before" exists, then "delete"
418             builder = ContentProviderOperation.newDelete(targetUri);
419             builder.withSelection(mIdColumn + "=" + getId(), null);
420         } else if (isUpdate()) {
421             // When has changes and "before" exists, then "update"
422             builder = ContentProviderOperation.newUpdate(targetUri);
423             builder.withSelection(mIdColumn + "=" + getId(), null);
424             builder.withValues(mAfter);
425         }
426         return builder;
427     }
428 
429     /** {@inheritDoc} */
describeContents()430     public int describeContents() {
431         // Nothing special about this parcel
432         return 0;
433     }
434 
435     /** {@inheritDoc} */
writeToParcel(Parcel dest, int flags)436     public void writeToParcel(Parcel dest, int flags) {
437         dest.writeParcelable(mBefore, flags);
438         dest.writeParcelable(mAfter, flags);
439         dest.writeString(mIdColumn);
440     }
441 
readFromParcel(Parcel source)442     public void readFromParcel(Parcel source) {
443         final ClassLoader loader = getClass().getClassLoader();
444         mBefore = source.<ContentValues> readParcelable(loader);
445         mAfter = source.<ContentValues> readParcelable(loader);
446         mIdColumn = source.readString();
447     }
448 
449     public static final Creator<ValuesDelta> CREATOR = new Creator<ValuesDelta>() {
450         public ValuesDelta createFromParcel(Parcel in) {
451             final ValuesDelta values = new ValuesDelta();
452             values.readFromParcel(in);
453             return values;
454         }
455 
456         public ValuesDelta[] newArray(int size) {
457             return new ValuesDelta[size];
458         }
459     };
460 
setGroupRowId(long groupId)461     public void setGroupRowId(long groupId) {
462         put(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId);
463     }
464 
getGroupRowId()465     public Long getGroupRowId() {
466         return getAsLong(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID);
467     }
468 
setPhoto(byte[] value)469     public void setPhoto(byte[] value) {
470         put(ContactsContract.CommonDataKinds.Photo.PHOTO, value);
471     }
472 
getPhoto()473     public byte[] getPhoto() {
474         return getAsByteArray(ContactsContract.CommonDataKinds.Photo.PHOTO);
475     }
476 
setSuperPrimary(boolean val)477     public void setSuperPrimary(boolean val) {
478         if (val) {
479             put(ContactsContract.Data.IS_SUPER_PRIMARY, 1);
480         } else {
481             put(ContactsContract.Data.IS_SUPER_PRIMARY, 0);
482         }
483     }
484 
setPhoneticFamilyName(String value)485     public void setPhoneticFamilyName(String value) {
486         put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME, value);
487     }
488 
setPhoneticMiddleName(String value)489     public void setPhoneticMiddleName(String value) {
490         put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME, value);
491     }
492 
setPhoneticGivenName(String value)493     public void setPhoneticGivenName(String value) {
494         put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME, value);
495     }
496 
getPhoneticFamilyName()497     public String getPhoneticFamilyName() {
498         return getAsString(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME);
499     }
500 
getPhoneticMiddleName()501     public String getPhoneticMiddleName() {
502         return getAsString(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME);
503     }
504 
getPhoneticGivenName()505     public String getPhoneticGivenName() {
506         return getAsString(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME);
507     }
508 
getDisplayName()509     public String getDisplayName() {
510         return getAsString(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME);
511     }
512 
setDisplayName(String name)513     public void setDisplayName(String name) {
514         if (name == null) {
515             putNull(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME);
516         } else {
517             put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
518         }
519     }
520 
copyStructuredNameFieldsFrom(ValuesDelta name)521     public void copyStructuredNameFieldsFrom(ValuesDelta name) {
522         copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME);
523 
524         copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME);
525         copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME);
526         copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PREFIX);
527         copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME);
528         copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.SUFFIX);
529 
530         copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME);
531         copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME);
532         copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME);
533 
534         copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.FULL_NAME_STYLE);
535         copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PHONETIC_NAME_STYLE);
536     }
537 
getPhoneNumber()538     public String getPhoneNumber() {
539         return getAsString(ContactsContract.CommonDataKinds.Phone.NUMBER);
540     }
541 
getPhoneNormalizedNumber()542     public String getPhoneNormalizedNumber() {
543         return getAsString(ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER);
544     }
545 
phoneHasType()546     public boolean phoneHasType() {
547         return containsKey(ContactsContract.CommonDataKinds.Phone.TYPE);
548     }
549 
getPhoneType()550     public int getPhoneType() {
551         return getAsInteger(ContactsContract.CommonDataKinds.Phone.TYPE);
552     }
553 
getPhoneLabel()554     public String getPhoneLabel() {
555         return getAsString(ContactsContract.CommonDataKinds.Phone.LABEL);
556     }
557 
getEmailData()558     public String getEmailData() {
559         return getAsString(ContactsContract.CommonDataKinds.Email.DATA);
560     }
561 
emailHasType()562     public boolean emailHasType() {
563         return containsKey(ContactsContract.CommonDataKinds.Email.TYPE);
564     }
565 
getEmailType()566     public int getEmailType() {
567         return getAsInteger(ContactsContract.CommonDataKinds.Email.TYPE);
568     }
569 
getEmailLabel()570     public String getEmailLabel() {
571         return getAsString(ContactsContract.CommonDataKinds.Email.LABEL);
572     }
573 }
574