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