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 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 */ 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 */ 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 82 public ContentValues getAfter() { 83 return mAfter; 84 } 85 86 public boolean containsKey(String key) { 87 return ((mAfter != null && mAfter.containsKey(key)) || 88 (mBefore != null && mBefore.containsKey(key))); 89 } 90 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 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 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 121 public Integer getAsInteger(String key) { 122 return getAsInteger(key, null); 123 } 124 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 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 150 public String getMimetype() { 151 return getAsString(ContactsContract.Data.MIMETYPE); 152 } 153 154 public Long getId() { 155 return getAsLong(mIdColumn); 156 } 157 158 public void setIdColumn(String idColumn) { 159 mIdColumn = idColumn; 160 } 161 162 public boolean isPrimary() { 163 final Long isPrimary = getAsLong(ContactsContract.Data.IS_PRIMARY); 164 return isPrimary == null ? false : isPrimary != 0; 165 } 166 167 public void setFromTemplate(boolean isFromTemplate) { 168 mFromTemplate = isFromTemplate; 169 } 170 171 public boolean isFromTemplate() { 172 return mFromTemplate; 173 } 174 175 public boolean isSuperPrimary() { 176 final Long isSuperPrimary = getAsLong(ContactsContract.Data.IS_SUPER_PRIMARY); 177 return isSuperPrimary == null ? false : isSuperPrimary != 0; 178 } 179 180 public boolean beforeExists() { 181 return (mBefore != null && mBefore.containsKey(mIdColumn)); 182 } 183 184 /** 185 * When "after" is present, then visible 186 */ 187 public boolean isVisible() { 188 return (mAfter != null); 189 } 190 191 /** 192 * When "after" is wiped, action is "delete" 193 */ 194 public boolean isDelete() { 195 return beforeExists() && (mAfter == null); 196 } 197 198 /** 199 * When no "before" or "after", is transient 200 */ 201 public boolean isTransient() { 202 return (mBefore == null) && (mAfter == null); 203 } 204 205 /** 206 * When "after" has some changes, action is "update" 207 */ 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 */ 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 */ 236 public boolean isInsert() { 237 return !beforeExists() && (mAfter != null); 238 } 239 240 public void markDeleted() { 241 mAfter = null; 242 } 243 244 /** 245 * Ensure that our internal structure is ready for storing updates. 246 */ 247 private void ensureUpdate() { 248 if (mAfter == null) { 249 mAfter = new ContentValues(); 250 } 251 } 252 253 public void put(String key, String value) { 254 ensureUpdate(); 255 mAfter.put(key, value); 256 } 257 258 public void put(String key, byte[] value) { 259 ensureUpdate(); 260 mAfter.put(key, value); 261 } 262 263 public void put(String key, int value) { 264 ensureUpdate(); 265 mAfter.put(key, value); 266 } 267 268 public void put(String key, long value) { 269 ensureUpdate(); 270 mAfter.put(key, value); 271 } 272 273 public void putNull(String key) { 274 ensureUpdate(); 275 mAfter.putNull(key); 276 } 277 278 public void copyStringFrom(ValuesDelta from, String key) { 279 ensureUpdate(); 280 if (containsKey(key) || from.containsKey(key)) { 281 put(key, from.getAsString(key)); 282 } 283 } 284 285 /** 286 * Return set of all keys defined through this object. 287 */ 288 public Set<String> keySet() { 289 final HashSet<String> keys = Sets.newHashSet(); 290 291 if (mBefore != null) { 292 for (Map.Entry<String, Object> entry : mBefore.valueSet()) { 293 keys.add(entry.getKey()); 294 } 295 } 296 297 if (mAfter != null) { 298 for (Map.Entry<String, Object> entry : mAfter.valueSet()) { 299 keys.add(entry.getKey()); 300 } 301 } 302 303 return keys; 304 } 305 306 /** 307 * Return complete set of "before" and "after" values mixed together, 308 * giving full state regardless of edits. 309 */ 310 public ContentValues getCompleteValues() { 311 final ContentValues values = new ContentValues(); 312 if (mBefore != null) { 313 values.putAll(mBefore); 314 } 315 if (mAfter != null) { 316 values.putAll(mAfter); 317 } 318 if (values.containsKey(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID)) { 319 // Clear to avoid double-definitions, and prefer rows 320 values.remove(ContactsContract.CommonDataKinds.GroupMembership.GROUP_SOURCE_ID); 321 } 322 323 return values; 324 } 325 326 /** 327 * Merge the "after" values from the given {@link ValuesDelta}, 328 * discarding any existing "after" state. This is typically used when 329 * re-parenting changes onto an updated {@link Entity}. 330 */ 331 public static ValuesDelta mergeAfter(ValuesDelta local, ValuesDelta remote) { 332 // Bail early if trying to merge delete with missing local 333 if (local == null && (remote.isDelete() || remote.isTransient())) return null; 334 335 // Create local version if none exists yet 336 if (local == null) local = new ValuesDelta(); 337 338 if (!local.beforeExists()) { 339 // Any "before" record is missing, so take all values as "insert" 340 local.mAfter = remote.getCompleteValues(); 341 } else { 342 // Existing "update" with only "after" values 343 local.mAfter = remote.mAfter; 344 } 345 346 return local; 347 } 348 349 @Override 350 public boolean equals(Object object) { 351 if (object instanceof ValuesDelta) { 352 // Only exactly equal with both are identical subsets 353 final ValuesDelta other = (ValuesDelta)object; 354 return this.subsetEquals(other) && other.subsetEquals(this); 355 } 356 return false; 357 } 358 359 @Override 360 public String toString() { 361 final StringBuilder builder = new StringBuilder(); 362 toString(builder); 363 return builder.toString(); 364 } 365 366 /** 367 * Helper for building string representation, leveraging the given 368 * {@link StringBuilder} to minimize allocations. 369 */ 370 public void toString(StringBuilder builder) { 371 builder.append("{ "); 372 builder.append("IdColumn="); 373 builder.append(mIdColumn); 374 builder.append(", FromTemplate="); 375 builder.append(mFromTemplate); 376 builder.append(", "); 377 for (String key : this.keySet()) { 378 builder.append(key); 379 builder.append("="); 380 builder.append(this.getAsString(key)); 381 builder.append(", "); 382 } 383 builder.append("}"); 384 } 385 386 /** 387 * Check if the given {@link ValuesDelta} is both a subset of this 388 * object, and any defined keys have equal values. 389 */ 390 public boolean subsetEquals(ValuesDelta other) { 391 for (String key : this.keySet()) { 392 final String ourValue = this.getAsString(key); 393 final String theirValue = other.getAsString(key); 394 if (ourValue == null) { 395 // If they have value when we're null, no match 396 if (theirValue != null) return false; 397 } else { 398 // If both values defined and aren't equal, no match 399 if (!ourValue.equals(theirValue)) return false; 400 } 401 } 402 // All values compared and matched 403 return true; 404 } 405 406 /** 407 * Build a {@link android.content.ContentProviderOperation} that will transform our 408 * "before" state into our "after" state, using insert, update, or 409 * delete as needed. 410 */ 411 public ContentProviderOperation.Builder buildDiff(Uri targetUri) { 412 ContentProviderOperation.Builder builder = null; 413 if (isInsert()) { 414 // Changed values are "insert" back-referenced to Contact 415 mAfter.remove(mIdColumn); 416 builder = ContentProviderOperation.newInsert(targetUri); 417 builder.withValues(mAfter); 418 } else if (isDelete()) { 419 // When marked for deletion and "before" exists, then "delete" 420 builder = ContentProviderOperation.newDelete(targetUri); 421 builder.withSelection(mIdColumn + "=" + getId(), null); 422 } else if (isUpdate()) { 423 // When has changes and "before" exists, then "update" 424 builder = ContentProviderOperation.newUpdate(targetUri); 425 builder.withSelection(mIdColumn + "=" + getId(), null); 426 builder.withValues(mAfter); 427 } 428 return builder; 429 } 430 431 /** {@inheritDoc} */ 432 public int describeContents() { 433 // Nothing special about this parcel 434 return 0; 435 } 436 437 /** {@inheritDoc} */ 438 public void writeToParcel(Parcel dest, int flags) { 439 dest.writeParcelable(mBefore, flags); 440 dest.writeParcelable(mAfter, flags); 441 dest.writeString(mIdColumn); 442 } 443 444 public void readFromParcel(Parcel source) { 445 final ClassLoader loader = getClass().getClassLoader(); 446 mBefore = source.<ContentValues> readParcelable(loader); 447 mAfter = source.<ContentValues> readParcelable(loader); 448 mIdColumn = source.readString(); 449 } 450 451 public static final Creator<ValuesDelta> CREATOR = new Creator<ValuesDelta>() { 452 public ValuesDelta createFromParcel(Parcel in) { 453 final ValuesDelta values = new ValuesDelta(); 454 values.readFromParcel(in); 455 return values; 456 } 457 458 public ValuesDelta[] newArray(int size) { 459 return new ValuesDelta[size]; 460 } 461 }; 462 463 public void setGroupRowId(long groupId) { 464 put(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId); 465 } 466 467 public Long getGroupRowId() { 468 return getAsLong(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID); 469 } 470 471 public void setPhoto(byte[] value) { 472 put(ContactsContract.CommonDataKinds.Photo.PHOTO, value); 473 } 474 475 public byte[] getPhoto() { 476 return getAsByteArray(ContactsContract.CommonDataKinds.Photo.PHOTO); 477 } 478 479 public void setSuperPrimary(boolean val) { 480 if (val) { 481 put(ContactsContract.Data.IS_SUPER_PRIMARY, 1); 482 } else { 483 put(ContactsContract.Data.IS_SUPER_PRIMARY, 0); 484 } 485 } 486 487 public void setPhoneticFamilyName(String value) { 488 put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME, value); 489 } 490 491 public void setPhoneticMiddleName(String value) { 492 put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME, value); 493 } 494 495 public void setPhoneticGivenName(String value) { 496 put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME, value); 497 } 498 499 public String getPhoneticFamilyName() { 500 return getAsString(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME); 501 } 502 503 public String getPhoneticMiddleName() { 504 return getAsString(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME); 505 } 506 507 public String getPhoneticGivenName() { 508 return getAsString(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME); 509 } 510 511 public String getDisplayName() { 512 return getAsString(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); 513 } 514 515 public void setDisplayName(String name) { 516 if (name == null) { 517 putNull(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); 518 } else { 519 put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name); 520 } 521 } 522 523 public void copyStructuredNameFieldsFrom(ValuesDelta name) { 524 copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); 525 526 copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME); 527 copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME); 528 copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PREFIX); 529 copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME); 530 copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.SUFFIX); 531 532 copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME); 533 copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME); 534 copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME); 535 536 copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.FULL_NAME_STYLE); 537 copyStringFrom(name, ContactsContract.Data.DATA11); 538 } 539 540 public String getPhoneNumber() { 541 return getAsString(ContactsContract.CommonDataKinds.Phone.NUMBER); 542 } 543 544 public String getPhoneNormalizedNumber() { 545 return getAsString(ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER); 546 } 547 548 public boolean phoneHasType() { 549 return containsKey(ContactsContract.CommonDataKinds.Phone.TYPE); 550 } 551 552 public int getPhoneType() { 553 return getAsInteger(ContactsContract.CommonDataKinds.Phone.TYPE); 554 } 555 556 public String getPhoneLabel() { 557 return getAsString(ContactsContract.CommonDataKinds.Phone.LABEL); 558 } 559 560 public String getEmailData() { 561 return getAsString(ContactsContract.CommonDataKinds.Email.DATA); 562 } 563 564 public boolean emailHasType() { 565 return containsKey(ContactsContract.CommonDataKinds.Email.TYPE); 566 } 567 568 public int getEmailType() { 569 return getAsInteger(ContactsContract.CommonDataKinds.Email.TYPE); 570 } 571 572 public String getEmailLabel() { 573 return getAsString(ContactsContract.CommonDataKinds.Email.LABEL); 574 } 575 } 576