Home | History | Annotate | Download | only in model
      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