blob: 120c205c90acdc1a43e44420b1a89f1e076e6ab2 [file] [log] [blame]
Chiao Cheng429b9112012-11-30 12:04:14 -08001/*
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
Gary Mai69c182a2016-12-05 13:07:03 -080017package com.android.contacts.model;
Chiao Cheng429b9112012-11-30 12:04:14 -080018
19import android.content.ContentProviderOperation;
20import android.content.ContentValues;
21import android.net.Uri;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.provider.BaseColumns;
25import android.provider.ContactsContract;
26
Gary Mai69c182a2016-12-05 13:07:03 -080027import com.android.contacts.compat.CompatUtils;
Gary Mai698cee72016-09-19 16:09:54 -070028
Chiao Cheng429b9112012-11-30 12:04:14 -080029import com.google.common.collect.Sets;
30
31import java.util.HashSet;
32import java.util.Map;
33import 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 */
40public 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
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 */
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 */
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
Chiao Cheng429b9112012-11-30 12:04:14 -080082 public ContentValues getAfter() {
83 return mAfter;
84 }
85
Gary Mai698cee72016-09-19 16:09:54 -070086 public ContentValues getBefore() {
87 return mBefore;
88 }
89
Chiao Cheng429b9112012-11-30 12:04:14 -080090 public boolean containsKey(String key) {
91 return ((mAfter != null && mAfter.containsKey(key)) ||
92 (mBefore != null && mBefore.containsKey(key)));
93 }
94
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
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
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
125 public Integer getAsInteger(String key) {
126 return getAsInteger(key, null);
127 }
128
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
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
154 public String getMimetype() {
155 return getAsString(ContactsContract.Data.MIMETYPE);
156 }
157
158 public Long getId() {
159 return getAsLong(mIdColumn);
160 }
161
162 public void setIdColumn(String idColumn) {
163 mIdColumn = idColumn;
164 }
165
166 public boolean isPrimary() {
167 final Long isPrimary = getAsLong(ContactsContract.Data.IS_PRIMARY);
168 return isPrimary == null ? false : isPrimary != 0;
169 }
170
171 public void setFromTemplate(boolean isFromTemplate) {
172 mFromTemplate = isFromTemplate;
173 }
174
175 public boolean isFromTemplate() {
176 return mFromTemplate;
177 }
178
179 public boolean isSuperPrimary() {
180 final Long isSuperPrimary = getAsLong(ContactsContract.Data.IS_SUPER_PRIMARY);
181 return isSuperPrimary == null ? false : isSuperPrimary != 0;
182 }
183
184 public boolean beforeExists() {
185 return (mBefore != null && mBefore.containsKey(mIdColumn));
186 }
187
188 /**
189 * When "after" is present, then visible
190 */
191 public boolean isVisible() {
192 return (mAfter != null);
193 }
194
195 /**
196 * When "after" is wiped, action is "delete"
197 */
198 public boolean isDelete() {
199 return beforeExists() && (mAfter == null);
200 }
201
202 /**
203 * When no "before" or "after", is transient
204 */
205 public boolean isTransient() {
206 return (mBefore == null) && (mAfter == null);
207 }
208
209 /**
210 * When "after" has some changes, action is "update"
211 */
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 */
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 */
240 public boolean isInsert() {
241 return !beforeExists() && (mAfter != null);
242 }
243
244 public void markDeleted() {
245 mAfter = null;
246 }
247
248 /**
249 * Ensure that our internal structure is ready for storing updates.
250 */
251 private void ensureUpdate() {
252 if (mAfter == null) {
253 mAfter = new ContentValues();
254 }
255 }
256
257 public void put(String key, String value) {
258 ensureUpdate();
259 mAfter.put(key, value);
260 }
261
262 public void put(String key, byte[] value) {
263 ensureUpdate();
264 mAfter.put(key, value);
265 }
266
267 public void put(String key, int value) {
268 ensureUpdate();
269 mAfter.put(key, value);
270 }
271
272 public void put(String key, long value) {
273 ensureUpdate();
274 mAfter.put(key, value);
275 }
276
277 public void putNull(String key) {
278 ensureUpdate();
279 mAfter.putNull(key);
280 }
281
282 public void copyStringFrom(ValuesDelta from, String key) {
283 ensureUpdate();
Jay Shrauneraa35e932015-07-30 12:12:02 -0700284 if (containsKey(key) || from.containsKey(key)) {
285 put(key, from.getAsString(key));
286 }
Chiao Cheng429b9112012-11-30 12:04:14 -0800287 }
288
289 /**
290 * Return set of all keys defined through this object.
291 */
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 */
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 */
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
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
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 */
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 */
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 */
415 public ContentProviderOperation.Builder buildDiff(Uri targetUri) {
Wenyi Wang93fdd482015-12-07 14:26:54 -0800416 return buildDiffHelper(targetUri);
417 }
418
419 /**
420 * For compatibility purpose.
421 */
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
435 private ContentProviderOperation.Builder buildDiffHelper(Uri targetUri) {
Chiao Cheng429b9112012-11-30 12:04:14 -0800436 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} */
456 public int describeContents() {
457 // Nothing special about this parcel
458 return 0;
459 }
460
461 /** {@inheritDoc} */
462 public void writeToParcel(Parcel dest, int flags) {
463 dest.writeParcelable(mBefore, flags);
464 dest.writeParcelable(mAfter, flags);
465 dest.writeString(mIdColumn);
466 }
467
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
487 public void setGroupRowId(long groupId) {
488 put(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId);
489 }
490
491 public Long getGroupRowId() {
492 return getAsLong(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID);
493 }
494
495 public void setPhoto(byte[] value) {
496 put(ContactsContract.CommonDataKinds.Photo.PHOTO, value);
497 }
498
499 public byte[] getPhoto() {
500 return getAsByteArray(ContactsContract.CommonDataKinds.Photo.PHOTO);
501 }
502
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
511 public void setPhoneticFamilyName(String value) {
512 put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME, value);
513 }
514
515 public void setPhoneticMiddleName(String value) {
516 put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME, value);
517 }
518
519 public void setPhoneticGivenName(String value) {
520 put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME, value);
521 }
522
523 public String getPhoneticFamilyName() {
524 return getAsString(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME);
525 }
526
527 public String getPhoneticMiddleName() {
528 return getAsString(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME);
529 }
530
531 public String getPhoneticGivenName() {
532 return getAsString(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME);
533 }
534
535 public String getDisplayName() {
536 return getAsString(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME);
537 }
538
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
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);
Jay Shrauneraa35e932015-07-30 12:12:02 -0700561 copyStringFrom(name, ContactsContract.Data.DATA11);
Chiao Cheng429b9112012-11-30 12:04:14 -0800562 }
563
564 public String getPhoneNumber() {
565 return getAsString(ContactsContract.CommonDataKinds.Phone.NUMBER);
566 }
567
568 public String getPhoneNormalizedNumber() {
569 return getAsString(ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER);
570 }
571
Jay Shrauner26d51ed2015-09-29 14:52:55 -0700572 public boolean hasPhoneType() {
573 return getPhoneType() != null;
Chiao Cheng429b9112012-11-30 12:04:14 -0800574 }
575
Jay Shrauner26d51ed2015-09-29 14:52:55 -0700576 public Integer getPhoneType() {
Chiao Cheng429b9112012-11-30 12:04:14 -0800577 return getAsInteger(ContactsContract.CommonDataKinds.Phone.TYPE);
578 }
579
580 public String getPhoneLabel() {
581 return getAsString(ContactsContract.CommonDataKinds.Phone.LABEL);
582 }
583
584 public String getEmailData() {
585 return getAsString(ContactsContract.CommonDataKinds.Email.DATA);
586 }
587
Jay Shrauner26d51ed2015-09-29 14:52:55 -0700588 public boolean hasEmailType() {
589 return getEmailType() != null;
Chiao Cheng429b9112012-11-30 12:04:14 -0800590 }
591
Jay Shrauner26d51ed2015-09-29 14:52:55 -0700592 public Integer getEmailType() {
Chiao Cheng429b9112012-11-30 12:04:14 -0800593 return getAsInteger(ContactsContract.CommonDataKinds.Email.TYPE);
594 }
595
596 public String getEmailLabel() {
597 return getAsString(ContactsContract.CommonDataKinds.Email.LABEL);
598 }
599}