Home | History | Annotate | Download | only in platform
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * 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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 package com.example.android.samplesync.platform;
     17 
     18 import com.example.android.samplesync.Constants;
     19 import com.example.android.samplesync.R;
     20 import com.example.android.samplesync.client.NetworkUtilities;
     21 
     22 import android.content.ContentProviderOperation;
     23 import android.content.ContentValues;
     24 import android.content.Context;
     25 import android.net.Uri;
     26 import android.provider.ContactsContract;
     27 import android.provider.ContactsContract.CommonDataKinds.Email;
     28 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
     29 import android.provider.ContactsContract.CommonDataKinds.Phone;
     30 import android.provider.ContactsContract.CommonDataKinds.Photo;
     31 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
     32 import android.provider.ContactsContract.Data;
     33 import android.provider.ContactsContract.RawContacts;
     34 import android.text.TextUtils;
     35 
     36 /**
     37  * Helper class for storing data in the platform content providers.
     38  */
     39 public class ContactOperations {
     40     private final ContentValues mValues;
     41     private final BatchOperation mBatchOperation;
     42     private final Context mContext;
     43     private boolean mIsSyncOperation;
     44     private long mRawContactId;
     45     private int mBackReference;
     46     private boolean mIsNewContact;
     47 
     48     /**
     49      * Since we're sending a lot of contact provider operations in a single
     50      * batched operation, we want to make sure that we "yield" periodically
     51      * so that the Contact Provider can write changes to the DB, and can
     52      * open a new transaction.  This prevents ANR (application not responding)
     53      * errors.  The recommended time to specify that a yield is permitted is
     54      * with the first operation on a particular contact.  So if we're updating
     55      * multiple fields for a single contact, we make sure that we call
     56      * withYieldAllowed(true) on the first field that we update. We use
     57      * mIsYieldAllowed to keep track of what value we should pass to
     58      * withYieldAllowed().
     59      */
     60     private boolean mIsYieldAllowed;
     61 
     62     /**
     63      * Returns an instance of ContactOperations instance for adding new contact
     64      * to the platform contacts provider.
     65      *
     66      * @param context the Authenticator Activity context
     67      * @param userId the userId of the sample SyncAdapter user object
     68      * @param accountName the username for the SyncAdapter account
     69      * @param isSyncOperation are we executing this as part of a sync operation?
     70      * @return instance of ContactOperations
     71      */
     72     public static ContactOperations createNewContact(Context context, long userId,
     73             String accountName, boolean isSyncOperation, BatchOperation batchOperation) {
     74         return new ContactOperations(context, userId, accountName, isSyncOperation, batchOperation);
     75     }
     76 
     77     /**
     78      * Returns an instance of ContactOperations for updating existing contact in
     79      * the platform contacts provider.
     80      *
     81      * @param context the Authenticator Activity context
     82      * @param rawContactId the unique Id of the existing rawContact
     83      * @param isSyncOperation are we executing this as part of a sync operation?
     84      * @return instance of ContactOperations
     85      */
     86     public static ContactOperations updateExistingContact(Context context, long rawContactId,
     87             boolean isSyncOperation, BatchOperation batchOperation) {
     88         return new ContactOperations(context, rawContactId, isSyncOperation, batchOperation);
     89     }
     90 
     91     public ContactOperations(Context context, boolean isSyncOperation,
     92             BatchOperation batchOperation) {
     93         mValues = new ContentValues();
     94         mIsYieldAllowed = true;
     95         mIsSyncOperation = isSyncOperation;
     96         mContext = context;
     97         mBatchOperation = batchOperation;
     98     }
     99 
    100     public ContactOperations(Context context, long userId, String accountName,
    101             boolean isSyncOperation, BatchOperation batchOperation) {
    102         this(context, isSyncOperation, batchOperation);
    103         mBackReference = mBatchOperation.size();
    104         mIsNewContact = true;
    105         mValues.put(RawContacts.SOURCE_ID, userId);
    106         mValues.put(RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
    107         mValues.put(RawContacts.ACCOUNT_NAME, accountName);
    108         ContentProviderOperation.Builder builder =
    109                 newInsertCpo(RawContacts.CONTENT_URI, mIsSyncOperation, true).withValues(mValues);
    110         mBatchOperation.add(builder.build());
    111     }
    112 
    113     public ContactOperations(Context context, long rawContactId, boolean isSyncOperation,
    114             BatchOperation batchOperation) {
    115         this(context, isSyncOperation, batchOperation);
    116         mIsNewContact = false;
    117         mRawContactId = rawContactId;
    118     }
    119 
    120     /**
    121      * Adds a contact name. We can take either a full name ("Bob Smith") or separated
    122      * first-name and last-name ("Bob" and "Smith").
    123      *
    124      * @param fullName The full name of the contact - typically from an edit form
    125      *      Can be null if firstName/lastName are specified.
    126      * @param firstName The first name of the contact - can be null if fullName
    127      *      is specified.
    128      * @param lastName The last name of the contact - can be null if fullName
    129      *      is specified.
    130      * @return instance of ContactOperations
    131      */
    132     public ContactOperations addName(String fullName, String firstName, String lastName) {
    133         mValues.clear();
    134 
    135         if (!TextUtils.isEmpty(fullName)) {
    136             mValues.put(StructuredName.DISPLAY_NAME, fullName);
    137             mValues.put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
    138         } else {
    139             if (!TextUtils.isEmpty(firstName)) {
    140                 mValues.put(StructuredName.GIVEN_NAME, firstName);
    141                 mValues.put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
    142             }
    143             if (!TextUtils.isEmpty(lastName)) {
    144                 mValues.put(StructuredName.FAMILY_NAME, lastName);
    145                 mValues.put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
    146             }
    147         }
    148         if (mValues.size() > 0) {
    149             addInsertOp();
    150         }
    151         return this;
    152     }
    153 
    154     /**
    155      * Adds an email
    156      *
    157      * @param the email address we're adding
    158      * @return instance of ContactOperations
    159      */
    160     public ContactOperations addEmail(String email) {
    161         mValues.clear();
    162         if (!TextUtils.isEmpty(email)) {
    163             mValues.put(Email.DATA, email);
    164             mValues.put(Email.TYPE, Email.TYPE_OTHER);
    165             mValues.put(Email.MIMETYPE, Email.CONTENT_ITEM_TYPE);
    166             addInsertOp();
    167         }
    168         return this;
    169     }
    170 
    171     /**
    172      * Adds a phone number
    173      *
    174      * @param phone new phone number for the contact
    175      * @param phoneType the type: cell, home, etc.
    176      * @return instance of ContactOperations
    177      */
    178     public ContactOperations addPhone(String phone, int phoneType) {
    179         mValues.clear();
    180         if (!TextUtils.isEmpty(phone)) {
    181             mValues.put(Phone.NUMBER, phone);
    182             mValues.put(Phone.TYPE, phoneType);
    183             mValues.put(Phone.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
    184             addInsertOp();
    185         }
    186         return this;
    187     }
    188 
    189     /**
    190      * Adds a group membership
    191      *
    192      * @param id The id of the group to assign
    193      * @return instance of ContactOperations
    194      */
    195     public ContactOperations addGroupMembership(long groupId) {
    196         mValues.clear();
    197         mValues.put(GroupMembership.GROUP_ROW_ID, groupId);
    198         mValues.put(GroupMembership.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
    199         addInsertOp();
    200         return this;
    201     }
    202 
    203     public ContactOperations addAvatar(String avatarUrl) {
    204         if (avatarUrl != null) {
    205             byte[] avatarBuffer = NetworkUtilities.downloadAvatar(avatarUrl);
    206             if (avatarBuffer != null) {
    207                 mValues.clear();
    208                 mValues.put(Photo.PHOTO, avatarBuffer);
    209                 mValues.put(Photo.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
    210                 addInsertOp();
    211             }
    212         }
    213         return this;
    214     }
    215 
    216     /**
    217      * Adds a profile action
    218      *
    219      * @param userId the userId of the sample SyncAdapter user object
    220      * @return instance of ContactOperations
    221      */
    222     public ContactOperations addProfileAction(long userId) {
    223         mValues.clear();
    224         if (userId != 0) {
    225             mValues.put(SampleSyncAdapterColumns.DATA_PID, userId);
    226             mValues.put(SampleSyncAdapterColumns.DATA_SUMMARY, mContext
    227                 .getString(R.string.profile_action));
    228             mValues.put(SampleSyncAdapterColumns.DATA_DETAIL, mContext
    229                 .getString(R.string.view_profile));
    230             mValues.put(Data.MIMETYPE, SampleSyncAdapterColumns.MIME_PROFILE);
    231             addInsertOp();
    232         }
    233         return this;
    234     }
    235 
    236     /**
    237      * Updates contact's serverId
    238      *
    239      * @param serverId the serverId for this contact
    240      * @param uri Uri for the existing raw contact to be updated
    241      * @return instance of ContactOperations
    242      */
    243     public ContactOperations updateServerId(long serverId, Uri uri) {
    244         mValues.clear();
    245         mValues.put(RawContacts.SOURCE_ID, serverId);
    246         addUpdateOp(uri);
    247         return this;
    248     }
    249 
    250     /**
    251      * Updates contact's email
    252      *
    253      * @param email email id of the sample SyncAdapter user
    254      * @param uri Uri for the existing raw contact to be updated
    255      * @return instance of ContactOperations
    256      */
    257     public ContactOperations updateEmail(String email, String existingEmail, Uri uri) {
    258         if (!TextUtils.equals(existingEmail, email)) {
    259             mValues.clear();
    260             mValues.put(Email.DATA, email);
    261             addUpdateOp(uri);
    262         }
    263         return this;
    264     }
    265 
    266     /**
    267      * Updates contact's name. The caller can either provide first-name
    268      * and last-name fields or a full-name field.
    269      *
    270      * @param uri Uri for the existing raw contact to be updated
    271      * @param existingFirstName the first name stored in provider
    272      * @param existingLastName the last name stored in provider
    273      * @param existingFullName the full name stored in provider
    274      * @param firstName the new first name to store
    275      * @param lastName the new last name to store
    276      * @param fullName the new full name to store
    277      * @return instance of ContactOperations
    278      */
    279     public ContactOperations updateName(Uri uri,
    280         String existingFirstName,
    281         String existingLastName,
    282         String existingFullName,
    283         String firstName,
    284         String lastName,
    285         String fullName) {
    286 
    287         mValues.clear();
    288         if (TextUtils.isEmpty(fullName)) {
    289             if (!TextUtils.equals(existingFirstName, firstName)) {
    290                 mValues.put(StructuredName.GIVEN_NAME, firstName);
    291             }
    292             if (!TextUtils.equals(existingLastName, lastName)) {
    293                 mValues.put(StructuredName.FAMILY_NAME, lastName);
    294             }
    295         } else {
    296             if (!TextUtils.equals(existingFullName, fullName)) {
    297                 mValues.put(StructuredName.DISPLAY_NAME, fullName);
    298             }
    299         }
    300         if (mValues.size() > 0) {
    301             addUpdateOp(uri);
    302         }
    303         return this;
    304     }
    305 
    306     public ContactOperations updateDirtyFlag(boolean isDirty, Uri uri) {
    307         int isDirtyValue = isDirty ? 1 : 0;
    308         mValues.clear();
    309         mValues.put(RawContacts.DIRTY, isDirtyValue);
    310         addUpdateOp(uri);
    311         return this;
    312     }
    313 
    314     /**
    315      * Updates contact's phone
    316      *
    317      * @param existingNumber phone number stored in contacts provider
    318      * @param phone new phone number for the contact
    319      * @param uri Uri for the existing raw contact to be updated
    320      * @return instance of ContactOperations
    321      */
    322     public ContactOperations updatePhone(String existingNumber, String phone, Uri uri) {
    323         if (!TextUtils.equals(phone, existingNumber)) {
    324             mValues.clear();
    325             mValues.put(Phone.NUMBER, phone);
    326             addUpdateOp(uri);
    327         }
    328         return this;
    329     }
    330 
    331     public ContactOperations updateAvatar(String avatarUrl, Uri uri) {
    332         if (avatarUrl != null) {
    333             byte[] avatarBuffer = NetworkUtilities.downloadAvatar(avatarUrl);
    334             if (avatarBuffer != null) {
    335                 mValues.clear();
    336                 mValues.put(Photo.PHOTO, avatarBuffer);
    337                 mValues.put(Photo.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
    338                 addUpdateOp(uri);
    339             }
    340         }
    341         return this;
    342     }
    343 
    344     /**
    345      * Updates contact's profile action
    346      *
    347      * @param userId sample SyncAdapter user id
    348      * @param uri Uri for the existing raw contact to be updated
    349      * @return instance of ContactOperations
    350      */
    351     public ContactOperations updateProfileAction(Integer userId, Uri uri) {
    352         mValues.clear();
    353         mValues.put(SampleSyncAdapterColumns.DATA_PID, userId);
    354         addUpdateOp(uri);
    355         return this;
    356     }
    357 
    358     /**
    359      * Adds an insert operation into the batch
    360      */
    361     private void addInsertOp() {
    362 
    363         if (!mIsNewContact) {
    364             mValues.put(Phone.RAW_CONTACT_ID, mRawContactId);
    365         }
    366         ContentProviderOperation.Builder builder =
    367                 newInsertCpo(Data.CONTENT_URI, mIsSyncOperation, mIsYieldAllowed);
    368         builder.withValues(mValues);
    369         if (mIsNewContact) {
    370             builder.withValueBackReference(Data.RAW_CONTACT_ID, mBackReference);
    371         }
    372         mIsYieldAllowed = false;
    373         mBatchOperation.add(builder.build());
    374     }
    375 
    376     /**
    377      * Adds an update operation into the batch
    378      */
    379     private void addUpdateOp(Uri uri) {
    380         ContentProviderOperation.Builder builder =
    381                 newUpdateCpo(uri, mIsSyncOperation, mIsYieldAllowed).withValues(mValues);
    382         mIsYieldAllowed = false;
    383         mBatchOperation.add(builder.build());
    384     }
    385 
    386     public static ContentProviderOperation.Builder newInsertCpo(Uri uri,
    387             boolean isSyncOperation, boolean isYieldAllowed) {
    388         return ContentProviderOperation
    389                 .newInsert(addCallerIsSyncAdapterParameter(uri, isSyncOperation))
    390                 .withYieldAllowed(isYieldAllowed);
    391     }
    392 
    393     public static ContentProviderOperation.Builder newUpdateCpo(Uri uri,
    394             boolean isSyncOperation, boolean isYieldAllowed) {
    395         return ContentProviderOperation
    396                 .newUpdate(addCallerIsSyncAdapterParameter(uri, isSyncOperation))
    397                 .withYieldAllowed(isYieldAllowed);
    398     }
    399 
    400     public static ContentProviderOperation.Builder newDeleteCpo(Uri uri,
    401             boolean isSyncOperation, boolean isYieldAllowed) {
    402         return ContentProviderOperation
    403                 .newDelete(addCallerIsSyncAdapterParameter(uri, isSyncOperation))
    404                 .withYieldAllowed(isYieldAllowed);
    405     }
    406 
    407     private static Uri addCallerIsSyncAdapterParameter(Uri uri, boolean isSyncOperation) {
    408         if (isSyncOperation) {
    409             // If we're in the middle of a real sync-adapter operation, then go ahead
    410             // and tell the Contacts provider that we're the sync adapter.  That
    411             // gives us some special permissions - like the ability to really
    412             // delete a contact, and the ability to clear the dirty flag.
    413             //
    414             // If we're not in the middle of a sync operation (for example, we just
    415             // locally created/edited a new contact), then we don't want to use
    416             // the special permissions, and the system will automagically mark
    417             // the contact as 'dirty' for us!
    418             return uri.buildUpon()
    419                     .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
    420                     .build();
    421         }
    422         return uri;
    423     }
    424 }
    425