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.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 
     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 
     82     public ContentValues getAfter() {
     83         return mAfter;
     84     }
     85 
     86     public ContentValues getBefore() {
     87         return mBefore;
     88     }
     89 
     90     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();
    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      */
    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) {
    416         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) {
    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} */
    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);
    561         copyStringFrom(name, ContactsContract.Data.DATA11);
    562     }
    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 
    572     public boolean hasPhoneType() {
    573         return getPhoneType() != null;
    574     }
    575 
    576     public Integer getPhoneType() {
    577         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 
    588     public boolean hasEmailType() {
    589         return getEmailType() != null;
    590     }
    591 
    592     public Integer getEmailType() {
    593         return getAsInteger(ContactsContract.CommonDataKinds.Email.TYPE);
    594     }
    595 
    596     public String getEmailLabel() {
    597         return getAsString(ContactsContract.CommonDataKinds.Email.LABEL);
    598     }
    599 }
    600