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