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