blob: 1445e55dd454f77e2e05b7c5d986f9eacff7c261 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.example.android.samplesync.platform;
import com.example.android.samplesync.Constants;
import com.example.android.samplesync.R;
import com.example.android.samplesync.client.NetworkUtilities;
import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
/**
* Helper class for storing data in the platform content providers.
*/
public class ContactOperations {
private final ContentValues mValues;
private final BatchOperation mBatchOperation;
private final Context mContext;
private boolean mIsSyncOperation;
private long mRawContactId;
private int mBackReference;
private boolean mIsNewContact;
/**
* Since we're sending a lot of contact provider operations in a single
* batched operation, we want to make sure that we "yield" periodically
* so that the Contact Provider can write changes to the DB, and can
* open a new transaction. This prevents ANR (application not responding)
* errors. The recommended time to specify that a yield is permitted is
* with the first operation on a particular contact. So if we're updating
* multiple fields for a single contact, we make sure that we call
* withYieldAllowed(true) on the first field that we update. We use
* mIsYieldAllowed to keep track of what value we should pass to
* withYieldAllowed().
*/
private boolean mIsYieldAllowed;
/**
* Returns an instance of ContactOperations instance for adding new contact
* to the platform contacts provider.
*
* @param context the Authenticator Activity context
* @param userId the userId of the sample SyncAdapter user object
* @param accountName the username for the SyncAdapter account
* @param isSyncOperation are we executing this as part of a sync operation?
* @return instance of ContactOperations
*/
public static ContactOperations createNewContact(Context context, long userId,
String accountName, boolean isSyncOperation, BatchOperation batchOperation) {
return new ContactOperations(context, userId, accountName, isSyncOperation, batchOperation);
}
/**
* Returns an instance of ContactOperations for updating existing contact in
* the platform contacts provider.
*
* @param context the Authenticator Activity context
* @param rawContactId the unique Id of the existing rawContact
* @param isSyncOperation are we executing this as part of a sync operation?
* @return instance of ContactOperations
*/
public static ContactOperations updateExistingContact(Context context, long rawContactId,
boolean isSyncOperation, BatchOperation batchOperation) {
return new ContactOperations(context, rawContactId, isSyncOperation, batchOperation);
}
public ContactOperations(Context context, boolean isSyncOperation,
BatchOperation batchOperation) {
mValues = new ContentValues();
mIsYieldAllowed = true;
mIsSyncOperation = isSyncOperation;
mContext = context;
mBatchOperation = batchOperation;
}
public ContactOperations(Context context, long userId, String accountName,
boolean isSyncOperation, BatchOperation batchOperation) {
this(context, isSyncOperation, batchOperation);
mBackReference = mBatchOperation.size();
mIsNewContact = true;
mValues.put(RawContacts.SOURCE_ID, userId);
mValues.put(RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
mValues.put(RawContacts.ACCOUNT_NAME, accountName);
ContentProviderOperation.Builder builder =
newInsertCpo(RawContacts.CONTENT_URI, mIsSyncOperation, true).withValues(mValues);
mBatchOperation.add(builder.build());
}
public ContactOperations(Context context, long rawContactId, boolean isSyncOperation,
BatchOperation batchOperation) {
this(context, isSyncOperation, batchOperation);
mIsNewContact = false;
mRawContactId = rawContactId;
}
/**
* Adds a contact name. We can take either a full name ("Bob Smith") or separated
* first-name and last-name ("Bob" and "Smith").
*
* @param fullName The full name of the contact - typically from an edit form
* Can be null if firstName/lastName are specified.
* @param firstName The first name of the contact - can be null if fullName
* is specified.
* @param lastName The last name of the contact - can be null if fullName
* is specified.
* @return instance of ContactOperations
*/
public ContactOperations addName(String fullName, String firstName, String lastName) {
mValues.clear();
if (!TextUtils.isEmpty(fullName)) {
mValues.put(StructuredName.DISPLAY_NAME, fullName);
mValues.put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
} else {
if (!TextUtils.isEmpty(firstName)) {
mValues.put(StructuredName.GIVEN_NAME, firstName);
mValues.put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
}
if (!TextUtils.isEmpty(lastName)) {
mValues.put(StructuredName.FAMILY_NAME, lastName);
mValues.put(StructuredName.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
}
}
if (mValues.size() > 0) {
addInsertOp();
}
return this;
}
/**
* Adds an email
*
* @param the email address we're adding
* @return instance of ContactOperations
*/
public ContactOperations addEmail(String email) {
mValues.clear();
if (!TextUtils.isEmpty(email)) {
mValues.put(Email.DATA, email);
mValues.put(Email.TYPE, Email.TYPE_OTHER);
mValues.put(Email.MIMETYPE, Email.CONTENT_ITEM_TYPE);
addInsertOp();
}
return this;
}
/**
* Adds a phone number
*
* @param phone new phone number for the contact
* @param phoneType the type: cell, home, etc.
* @return instance of ContactOperations
*/
public ContactOperations addPhone(String phone, int phoneType) {
mValues.clear();
if (!TextUtils.isEmpty(phone)) {
mValues.put(Phone.NUMBER, phone);
mValues.put(Phone.TYPE, phoneType);
mValues.put(Phone.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
addInsertOp();
}
return this;
}
/**
* Adds a group membership
*
* @param id The id of the group to assign
* @return instance of ContactOperations
*/
public ContactOperations addGroupMembership(long groupId) {
mValues.clear();
mValues.put(GroupMembership.GROUP_ROW_ID, groupId);
mValues.put(GroupMembership.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
addInsertOp();
return this;
}
public ContactOperations addAvatar(String avatarUrl) {
if (avatarUrl != null) {
byte[] avatarBuffer = NetworkUtilities.downloadAvatar(avatarUrl);
if (avatarBuffer != null) {
mValues.clear();
mValues.put(Photo.PHOTO, avatarBuffer);
mValues.put(Photo.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
addInsertOp();
}
}
return this;
}
/**
* Adds a profile action
*
* @param userId the userId of the sample SyncAdapter user object
* @return instance of ContactOperations
*/
public ContactOperations addProfileAction(long userId) {
mValues.clear();
if (userId != 0) {
mValues.put(SampleSyncAdapterColumns.DATA_PID, userId);
mValues.put(SampleSyncAdapterColumns.DATA_SUMMARY, mContext
.getString(R.string.profile_action));
mValues.put(SampleSyncAdapterColumns.DATA_DETAIL, mContext
.getString(R.string.view_profile));
mValues.put(Data.MIMETYPE, SampleSyncAdapterColumns.MIME_PROFILE);
addInsertOp();
}
return this;
}
/**
* Updates contact's serverId
*
* @param serverId the serverId for this contact
* @param uri Uri for the existing raw contact to be updated
* @return instance of ContactOperations
*/
public ContactOperations updateServerId(long serverId, Uri uri) {
mValues.clear();
mValues.put(RawContacts.SOURCE_ID, serverId);
addUpdateOp(uri);
return this;
}
/**
* Updates contact's email
*
* @param email email id of the sample SyncAdapter user
* @param uri Uri for the existing raw contact to be updated
* @return instance of ContactOperations
*/
public ContactOperations updateEmail(String email, String existingEmail, Uri uri) {
if (!TextUtils.equals(existingEmail, email)) {
mValues.clear();
mValues.put(Email.DATA, email);
addUpdateOp(uri);
}
return this;
}
/**
* Updates contact's name. The caller can either provide first-name
* and last-name fields or a full-name field.
*
* @param uri Uri for the existing raw contact to be updated
* @param existingFirstName the first name stored in provider
* @param existingLastName the last name stored in provider
* @param existingFullName the full name stored in provider
* @param firstName the new first name to store
* @param lastName the new last name to store
* @param fullName the new full name to store
* @return instance of ContactOperations
*/
public ContactOperations updateName(Uri uri,
String existingFirstName,
String existingLastName,
String existingFullName,
String firstName,
String lastName,
String fullName) {
mValues.clear();
if (TextUtils.isEmpty(fullName)) {
if (!TextUtils.equals(existingFirstName, firstName)) {
mValues.put(StructuredName.GIVEN_NAME, firstName);
}
if (!TextUtils.equals(existingLastName, lastName)) {
mValues.put(StructuredName.FAMILY_NAME, lastName);
}
} else {
if (!TextUtils.equals(existingFullName, fullName)) {
mValues.put(StructuredName.DISPLAY_NAME, fullName);
}
}
if (mValues.size() > 0) {
addUpdateOp(uri);
}
return this;
}
public ContactOperations updateDirtyFlag(boolean isDirty, Uri uri) {
int isDirtyValue = isDirty ? 1 : 0;
mValues.clear();
mValues.put(RawContacts.DIRTY, isDirtyValue);
addUpdateOp(uri);
return this;
}
/**
* Updates contact's phone
*
* @param existingNumber phone number stored in contacts provider
* @param phone new phone number for the contact
* @param uri Uri for the existing raw contact to be updated
* @return instance of ContactOperations
*/
public ContactOperations updatePhone(String existingNumber, String phone, Uri uri) {
if (!TextUtils.equals(phone, existingNumber)) {
mValues.clear();
mValues.put(Phone.NUMBER, phone);
addUpdateOp(uri);
}
return this;
}
public ContactOperations updateAvatar(String avatarUrl, Uri uri) {
if (avatarUrl != null) {
byte[] avatarBuffer = NetworkUtilities.downloadAvatar(avatarUrl);
if (avatarBuffer != null) {
mValues.clear();
mValues.put(Photo.PHOTO, avatarBuffer);
mValues.put(Photo.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
addUpdateOp(uri);
}
}
return this;
}
/**
* Updates contact's profile action
*
* @param userId sample SyncAdapter user id
* @param uri Uri for the existing raw contact to be updated
* @return instance of ContactOperations
*/
public ContactOperations updateProfileAction(Integer userId, Uri uri) {
mValues.clear();
mValues.put(SampleSyncAdapterColumns.DATA_PID, userId);
addUpdateOp(uri);
return this;
}
/**
* Adds an insert operation into the batch
*/
private void addInsertOp() {
if (!mIsNewContact) {
mValues.put(Phone.RAW_CONTACT_ID, mRawContactId);
}
ContentProviderOperation.Builder builder =
newInsertCpo(Data.CONTENT_URI, mIsSyncOperation, mIsYieldAllowed);
builder.withValues(mValues);
if (mIsNewContact) {
builder.withValueBackReference(Data.RAW_CONTACT_ID, mBackReference);
}
mIsYieldAllowed = false;
mBatchOperation.add(builder.build());
}
/**
* Adds an update operation into the batch
*/
private void addUpdateOp(Uri uri) {
ContentProviderOperation.Builder builder =
newUpdateCpo(uri, mIsSyncOperation, mIsYieldAllowed).withValues(mValues);
mIsYieldAllowed = false;
mBatchOperation.add(builder.build());
}
public static ContentProviderOperation.Builder newInsertCpo(Uri uri,
boolean isSyncOperation, boolean isYieldAllowed) {
return ContentProviderOperation
.newInsert(addCallerIsSyncAdapterParameter(uri, isSyncOperation))
.withYieldAllowed(isYieldAllowed);
}
public static ContentProviderOperation.Builder newUpdateCpo(Uri uri,
boolean isSyncOperation, boolean isYieldAllowed) {
return ContentProviderOperation
.newUpdate(addCallerIsSyncAdapterParameter(uri, isSyncOperation))
.withYieldAllowed(isYieldAllowed);
}
public static ContentProviderOperation.Builder newDeleteCpo(Uri uri,
boolean isSyncOperation, boolean isYieldAllowed) {
return ContentProviderOperation
.newDelete(addCallerIsSyncAdapterParameter(uri, isSyncOperation))
.withYieldAllowed(isYieldAllowed);
}
private static Uri addCallerIsSyncAdapterParameter(Uri uri, boolean isSyncOperation) {
if (isSyncOperation) {
// If we're in the middle of a real sync-adapter operation, then go ahead
// and tell the Contacts provider that we're the sync adapter. That
// gives us some special permissions - like the ability to really
// delete a contact, and the ability to clear the dirty flag.
//
// If we're not in the middle of a sync operation (for example, we just
// locally created/edited a new contact), then we don't want to use
// the special permissions, and the system will automagically mark
// the contact as 'dirty' for us!
return uri.buildUpon()
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
.build();
}
return uri;
}
}