/*
* *
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* /
*/
package org.anhonesteffort.flock.sync.addressbook;
import android.accounts.Account;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.util.Log;
import android.util.Pair;
import ezvcard.VCard;
import ezvcard.parameter.ImageType;
import ezvcard.property.Photo;
import org.anhonesteffort.flock.util.guava.Optional;
import org.anhonesteffort.flock.sync.InvalidLocalComponentException;
import org.anhonesteffort.flock.sync.InvalidRemoteComponentException;
import org.anhonesteffort.flock.webdav.carddav.CardDavConstants;
import org.anhonesteffort.flock.sync.AbstractLocalComponentCollection;
import org.anhonesteffort.flock.webdav.ComponentETagPair;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* Programmer: rhodey
*/
public class LocalContactCollection extends AbstractLocalComponentCollection<VCard> {
private static final String TAG = "org.anhonesteffort.flock.sync.addressbook.LocalContactCollection";
private static final String PREFERENCES_NAME = "org.anhonesteffort.flock.sync.addressbook.LocalContactCollection";
private static final String KEY_PREFIX_COLLECTION_C_TAG = "LocalContactCollection.KEY_PREFIX_COLLECTION_C_TAG";
private static final String KEY_COLLECTION_DISPLAY_NAME = "LocalContactCollection.KEY_COLLECTION_DISPLAY_NAME";
private Context context;
public LocalContactCollection(Context context,
ContentProviderClient client,
Account account,
String remotePath)
{
super(client, account, remotePath, 1L); // hack :D limit one collection
this.context = context;
}
private String getKeyForCTag() {
return KEY_PREFIX_COLLECTION_C_TAG.concat(getPath());
}
private static SharedPreferences getSharedPreferences(Context context) {
return context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
}
public static Uri getSyncAdapterUri(Uri base, Account account) {
if (account != null)
return base.buildUpon()
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type)
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
.build();
return base.buildUpon()
.appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true")
.build();
}
@Override
protected Uri getSyncAdapterUri(Uri base) {
return getSyncAdapterUri(base, account);
}
@Override
protected Uri handleAddAccountQueryParams(Uri uri) {
return uri.buildUpon()
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_NAME, account.name)
.appendQueryParameter(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type)
.build();
}
@Override
protected Uri getUriForComponents() {
return getSyncAdapterUri(ContactsContract.RawContacts.CONTENT_URI);
}
private Uri getUriForData() {
return getSyncAdapterUri(ContactsContract.Data.CONTENT_URI);
}
private static Uri getUriForDisplayPhoto(Long rawContactId) {
Uri rawContactUri = ContentUris.withAppendedId(
ContactsContract.RawContacts.CONTENT_URI,
rawContactId
);
return Uri.withAppendedPath(rawContactUri, ContactsContract.Contacts.Photo.DISPLAY_PHOTO);
}
@Override
protected String getColumnNameCollectionLocalId() {
return "1"; // hack :D
}
@Override
protected String getColumnNameComponentLocalId() {
return ContactsContract.RawContacts._ID;
}
protected String getColumnNameComponentDataLocalId() {
return ContactsContract.Data.RAW_CONTACT_ID;
}
@Override
protected String getColumnNameComponentUid() {
return ContactFactory.COLUMN_NAME_CONTACT_UID;
}
@Override
protected String getColumnNameComponentETag() {
return ContactFactory.COLUMN_NAME_CONTACT_ETAG;
}
@Override
protected String getColumnNameDirty() {
return ContactsContract.RawContacts.DIRTY;
}
@Override
protected String getColumnNameDeleted() {
return ContactsContract.RawContacts.DELETED;
}
@Override
protected String getColumnNameAccountType() {
return ContactsContract.RawContacts.ACCOUNT_TYPE;
}
@Override
public Optional<String> getDisplayName() {
return Optional.fromNullable(
getSharedPreferences(context).getString(KEY_COLLECTION_DISPLAY_NAME, null)
);
}
@Override
public void setDisplayName(String displayName) {
getSharedPreferences(context).edit().putString(
KEY_COLLECTION_DISPLAY_NAME, displayName
).apply();
}
@Override
public Optional<String> getCTag() {
return Optional.fromNullable(
getSharedPreferences(context).getString(getKeyForCTag(), null)
);
}
@Override
public void setCTag(String cTag) {
getSharedPreferences(context).edit().putString(
getKeyForCTag(), cTag
).apply();
}
private void addStructuredNames(Long rawContactId, VCard vCard) throws RemoteException {
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] SELECTION_ARGS = new String[]{
rawContactId.toString(),
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
};
Cursor cursor = client.query(getUriForData(),
ContactFactory.getProjectionForStructuredName(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
ContentValues structuredNameValues = ContactFactory.getValuesForStructuredName(cursor);
ContactFactory.addStructuredName(vCard, structuredNameValues);
}
cursor.close();
}
private void addPhoneNumbers(Long rawContactId, VCard vCard)
throws RemoteException
{
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] SELECTION_ARGS = new String[]{
rawContactId.toString(),
ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
};
Cursor cursor = client.query(getUriForData(),
ContactFactory.getProjectionForPhoneNumber(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
ContentValues phoneNumberValues = ContactFactory.getValuesForPhoneNumber(cursor);
ContactFactory.addPhoneNumber(vCard, phoneNumberValues);
}
cursor.close();
}
private void addEmailAddresses(Long rawContactId, VCard vCard)
throws RemoteException
{
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] SELECTION_ARGS = new String[]{
rawContactId.toString(),
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
};
Cursor cursor = client.query(getUriForData(),
ContactFactory.getProjectionForEmailAddress(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
ContentValues emailAddressValues = ContactFactory.getValuesForEmailAddress(cursor);
ContactFactory.addEmailAddress(vCard, emailAddressValues);
}
cursor.close();
}
private Optional<Photo> getDisplayPhoto(Long rawContactId)
throws RemoteException
{
try {
AssetFileDescriptor fileDescriptor = client.openAssetFile(getUriForDisplayPhoto(rawContactId), "r");
Bitmap bitMap = BitmapFactory.decodeFileDescriptor(fileDescriptor.getFileDescriptor());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bitMap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
return Optional.of(
new Photo(outputStream.toByteArray(), ImageType.PNG)
);
} catch (FileNotFoundException e) {
return Optional.absent();
}
}
private Optional<Photo> getDataRowsPhoto(Long rawContactId) throws RemoteException {
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] SELECTION_ARGS = new String[] {
rawContactId.toString(),
ContactsContract.CommonDataKinds.Photo.CONTENT_ITEM_TYPE
};
Optional<Photo> photo = Optional.absent();
Cursor cursor = client.query(getUriForData(),
ContactFactory.getProjectionForPhoto(),
SELECTION,
SELECTION_ARGS,
null);
if (cursor.moveToNext()) {
ContentValues photoValues = ContactFactory.getValuesForPhoto(cursor);
photo = ContactFactory.getPhotoForValues(photoValues);
}
cursor.close();
return photo;
}
private void addPhotos(Long rawContactId, VCard vCard)
throws RemoteException
{
Optional<Photo> photo = getDisplayPhoto(rawContactId);
if (!photo.isPresent())
photo = getDataRowsPhoto(rawContactId);
if (photo.isPresent())
vCard.addPhoto(photo.get());
}
private void addOrganizations(Long rawContactId, VCard vCard) throws RemoteException {
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] SELECTION_ARGS = new String[]{
rawContactId.toString(),
ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE
};
Cursor cursor = client.query(getUriForData(),
ContactFactory.getProjectionForOrganization(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
ContentValues organizationValues = ContactFactory.getValuesForOrganization(cursor);
ContactFactory.addOrganization(vCard, organizationValues);
}
cursor.close();
}
private void addInstantMessaging(Long rawContactId, VCard vCard)
throws RemoteException
{
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] SELECTION_ARGS = new String[]{
rawContactId.toString(),
ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
};
Cursor cursor = client.query(getUriForData(),
ContactFactory.getProjectionForInstantMessaging(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
ContentValues instantMessagingValues = ContactFactory.getValuesForInstantMessaging(cursor);
ContactFactory.addInstantMessaging(vCard, instantMessagingValues);
}
cursor.close();
}
private void addNickNames(Long rawContactId, VCard vCard) throws RemoteException {
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] SELECTION_ARGS = new String[]{
rawContactId.toString(),
ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE
};
Cursor cursor = client.query(getUriForData(),
ContactFactory.getProjectionForNickName(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
ContentValues nickNameValues = ContactFactory.getValuesForNickName(cursor);
ContactFactory.addNickName(vCard, nickNameValues);
}
cursor.close();
}
private void addNotes(Long rawContactId, VCard vCard) throws RemoteException {
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] SELECTION_ARGS = new String[]{
rawContactId.toString(),
ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE
};
Cursor cursor = client.query(getUriForData(),
ContactFactory.getProjectionForNote(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
ContentValues noteValues = ContactFactory.getValuesForNote(cursor);
ContactFactory.addNote(vCard, noteValues);
}
cursor.close();
}
private void addPostalAddresses(Long rawContactId, VCard vCard)
throws RemoteException
{
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] SELECTION_ARGS = new String[]{
rawContactId.toString(),
ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE
};
Cursor cursor = client.query(getUriForData(),
ContactFactory.getProjectionForPostalAddress(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
ContentValues postalAddressValues = ContactFactory.getValuesForPostalAddress(cursor);
ContactFactory.addPostalAddress(vCard, postalAddressValues);
}
cursor.close();
}
private void addWebsites(Long rawContactId, VCard vCard)
throws RemoteException
{
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] SELECTION_ARGS = new String[]{
rawContactId.toString(),
ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE
};
Cursor cursor = client.query(getUriForData(),
ContactFactory.getProjectionForWebsite(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
ContentValues websiteValues = ContactFactory.getValuesForWebsite(cursor);
ContactFactory.addWebsite(vCard, websiteValues);
}
cursor.close();
}
private void addEvents(Long rawContactId, VCard vCard)
throws InvalidLocalComponentException, RemoteException
{
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] SELECTION_ARGS = new String[]{
rawContactId.toString(),
ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE
};
Cursor cursor = client.query(getUriForData(),
ContactFactory.getProjectionForEvent(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
ContentValues eventValues = ContactFactory.getValuesForEvent(cursor);
ContactFactory.addEvent(getPath(), vCard, eventValues);
}
cursor.close();
}
private void addSipAddresses(Long rawContactId, VCard vCard) throws RemoteException {
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] SELECTION_ARGS = new String[]{
rawContactId.toString(),
ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE
};
Cursor cursor = client.query(getUriForData(),
ContactFactory.getProjectionForSipAddress(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
ContentValues sipAddressValues = ContactFactory.getValuesForSipAddress(cursor);
ContactFactory.addSipAddress(vCard, sipAddressValues);
}
cursor.close();
}
private List<Long> getGroupIdsForContact(Long rawContactId) throws RemoteException {
String SELECTION = getColumnNameComponentDataLocalId() + "=? " +
"AND " + ContactsContract.Data.MIMETYPE + "=?";
String[] SELECTION_ARGS = new String[] {
rawContactId.toString(),
ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE
};
List<Long> groupIds = new LinkedList<Long>();
Cursor cursor = client.query(getUriForData(),
ContactFactory.getProjectionForGroupMembership(),
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext())
groupIds.add(ContactFactory.getIdForGroupMembership(cursor));
cursor.close();
return groupIds;
}
private boolean isContactWithoutGroupVisible() throws RemoteException {
boolean contactWithoutGroupVisible = true;
Cursor cursor = client.query(getSyncAdapterUri(ContactsContract.Settings.CONTENT_URI),
new String[] {
ContactsContract.Settings.UNGROUPED_VISIBLE,
},
null,
null,
null);
if (cursor.moveToNext())
contactWithoutGroupVisible = cursor.getInt(0) != 0;
cursor.close();
return contactWithoutGroupVisible;
}
private void addInvisibleProperty(Long rawContactId, VCard vCard) throws RemoteException {
boolean isMemberOfGroup = getGroupIdsForContact(rawContactId).size() > 0;
if (!isMemberOfGroup && !isContactWithoutGroupVisible())
ContactFactory.addInvisibleProperty(vCard);
}
private List<Pair<Integer, Long>> getAggregateExceptionTypeIdPairs(Long rawContactId)
throws RemoteException
{
String[] PROJECTION = new String[] {
ContactsContract.AggregationExceptions.TYPE,
ContactsContract.AggregationExceptions.RAW_CONTACT_ID1,
ContactsContract.AggregationExceptions.RAW_CONTACT_ID2
};
String SELECTION = ContactsContract.AggregationExceptions.RAW_CONTACT_ID1 + "=? OR " +
ContactsContract.AggregationExceptions.RAW_CONTACT_ID2 + "=?";
String[] SELECTION_ARGS = new String[] { rawContactId.toString(), rawContactId.toString() };
List<Pair<Integer, Long>> typeIdPairs = new LinkedList<Pair<Integer, Long>>();
Cursor cursor = client.query(ContactsContract.AggregationExceptions.CONTENT_URI,
PROJECTION,
SELECTION,
SELECTION_ARGS,
null);
while (cursor.moveToNext()) {
if (cursor.getLong(1) != rawContactId)
typeIdPairs.add(new Pair<Integer, Long>(cursor.getInt(0), cursor.getLong(1)));
else
typeIdPairs.add(new Pair<Integer, Long>(cursor.getInt(0), cursor.getLong(2)));
}
cursor.close();
return typeIdPairs;
}
private Optional<Pair<Account, String>> getAccountUidPairForRawContact(Long rawContactId)
throws RemoteException
{
String[] PROJECTION = new String[] {
ContactsContract.RawContacts.ACCOUNT_NAME,
ContactsContract.RawContacts.ACCOUNT_TYPE,
ContactFactory.COLUMN_NAME_CONTACT_UID
};
String SELECTION = ContactsContract.RawContacts._ID + "=? ";
String[] SELECTION_ARGS = new String[] { rawContactId.toString() };
Pair<Account, String> accountUidPair = null;
Cursor cursor = client.query(ContactsContract.RawContacts.CONTENT_URI,
PROJECTION,
SELECTION,
SELECTION_ARGS,
null);
if (cursor.moveToNext()) {
if (cursor.getString(2) != null) {
accountUidPair = new Pair<Account, String>(
new Account(cursor.getString(0), cursor.getString(1)),
cursor.getString(2)
);
}
else {
Log.e(TAG, "raw contact " + rawContactId +
" has no SOURCE_ID :( is likely a local contact, must ignore.");
}
}
cursor.close();
return Optional.fromNullable(accountUidPair);
}
private void addAggregationExceptions(Long rawContactId, VCard vCard) throws RemoteException {
List<ContactFactory.AggregationException> exceptions = new LinkedList<ContactFactory.AggregationException>();
List<Pair<Integer, Long>> typeIdPairs = getAggregateExceptionTypeIdPairs(rawContactId);
for (Pair<Integer, Long> typeIdPair : typeIdPairs) {
Optional<Pair<Account, String>> accountUidPair = getAccountUidPairForRawContact(typeIdPair.second);
if (accountUidPair.isPresent()) {
exceptions.add(new ContactFactory.AggregationException(
typeIdPair.first,
accountUidPair.get().first,
accountUidPair.get().second
));
}
else
Log.e(TAG, "accountUidPair for raw contact " + typeIdPair.second + " is not present :(");
}
ContactFactory.addAggregationExceptions(vCard, exceptions);
}
private void buildContact(Long rawContactId, VCard vCard)
throws InvalidLocalComponentException, RemoteException
{
addStructuredNames( rawContactId, vCard);
addPhoneNumbers( rawContactId, vCard);
addEmailAddresses( rawContactId, vCard);
addPhotos( rawContactId, vCard);
addOrganizations( rawContactId, vCard);
addInstantMessaging( rawContactId, vCard);
addNickNames( rawContactId, vCard);
addNotes( rawContactId, vCard);
addPostalAddresses( rawContactId, vCard);
addWebsites( rawContactId, vCard);
addEvents( rawContactId, vCard);
addSipAddresses( rawContactId, vCard);
addInvisibleProperty( rawContactId, vCard);
addAggregationExceptions(rawContactId, vCard);
}
@Override
public Optional<VCard> getComponent(Long rawContactId)
throws InvalidLocalComponentException, RemoteException
{
Cursor cursor = client.query(ContentUris.withAppendedId(getUriForComponents(), rawContactId),
ContactFactory.getProjectionForRawContact(),
null,
null,
null);
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
if (cursor.moveToNext()) {
ContentValues rawContactValues = ContactFactory.getValuesForRawContact(cursor);
ComponentETagPair<VCard> vCard = ContactFactory.getVCard(rawContactValues);
buildContact(rawContactId, vCard.getComponent());
cursor.close();
return Optional.of(vCard.getComponent());
}
cursor.close();
return Optional.absent();
}
@Override
public Optional<ComponentETagPair<VCard>> getComponent(String uid)
throws InvalidLocalComponentException, RemoteException
{
String SELECTION = getColumnNameComponentUid() + "=?";
String[] SELECTION_ARGS = new String[]{uid};
Cursor cursor = client.query(getUriForComponents(),
ContactFactory.getProjectionForRawContact(),
SELECTION,
SELECTION_ARGS,
null);
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
if (cursor.moveToNext()) {
ContentValues rawContactValues = ContactFactory.getValuesForRawContact(cursor);
Long rawContactId = rawContactValues.getAsLong(getColumnNameComponentLocalId());
ComponentETagPair<VCard> vCard = ContactFactory.getVCard(rawContactValues);
buildContact(rawContactId, vCard.getComponent());
cursor.close();
return Optional.of(vCard);
}
cursor.close();
return Optional.absent();
}
@Override
public List<ComponentETagPair<VCard>> getComponents()
throws InvalidLocalComponentException, RemoteException
{
List<ComponentETagPair<VCard>> vCards = new LinkedList<ComponentETagPair<VCard>>();
Cursor cursor = client.query(getUriForComponents(),
ContactFactory.getProjectionForRawContact(),
null, null, null);
if (cursor == null)
throw new RemoteException("Content provider client gave us a null cursor!");
while (cursor.moveToNext()) {
ContentValues rawContactValues = ContactFactory.getValuesForRawContact(cursor);
Long rawContactId = rawContactValues.getAsLong(getColumnNameComponentLocalId());
ComponentETagPair<VCard> vCard = ContactFactory.getVCard(rawContactValues);
buildContact(rawContactId, vCard.getComponent());
vCards.add(vCard);
}
cursor.close();
return vCards;
}
private ArrayList<ContentProviderOperation> getOperationsForAggregationExceptions(VCard vCard,
int idBackReference)
throws RemoteException
{
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
List<ContactFactory.AggregationException> exceptions = null;
try {
exceptions = ContactFactory.getAggregationExceptions(vCard);
if (exceptions.isEmpty())
return operations;
} catch (IllegalArgumentException e) {
Log.e(TAG, "error parsing aggregation exceptions from vCard, ignoring :(", e);
return operations;
}
Log.d(TAG, "need to insert values for " + exceptions.size() + " aggregation exceptions.");
for (ContactFactory.AggregationException exception : exceptions) {
LocalContactCollection otherCollection =
new LocalContactCollection(context, client, exception.getContactAccount(), "hack");
Optional<Long> exceptionLocalId =
otherCollection.getLocalIdForUid(exception.getContactUid());
if (exceptionLocalId.isPresent()) {
ContentValues values = new ContentValues(2);
values.put(ContactsContract.AggregationExceptions.TYPE, exception.getType());
values.put(ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, exceptionLocalId.get());
operations.add(
ContentProviderOperation.newUpdate(ContactsContract.AggregationExceptions.CONTENT_URI)
.withValues(values)
.withValueBackReference(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1, idBackReference)
.build()
);
}
else {
Log.e(TAG, "exceptionLocalId is not present for raw contact " + exception.getContactUid() +
" of account (" + exception.getContactAccount().name + ", " +
exception.getContactAccount().type + ")");
}
}
return operations;
}
@Override
public int addComponent(ComponentETagPair<VCard> vCard) throws RemoteException {
int rawContactOpIndex = operationQueue.size();
ContentValues rawContactValues = ContactFactory.getValuesForRawContact(vCard);
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForComponents())
.withValues(rawContactValues)
.build(),
128
);
Optional<ContentValues> structuredName = ContactFactory.getValuesForStructuredName(vCard.getComponent());
if (structuredName.isPresent()) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(structuredName.get())
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> phoneNumbers = ContactFactory.getValuesForPhoneNumbers(vCard.getComponent());
for (ContentValues phoneNumber : phoneNumbers) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(phoneNumber)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
128);
}
List<ContentValues> emailAddresses = ContactFactory.getValuesForEmailAddresses(vCard.getComponent());
for (ContentValues emailAddress : emailAddresses) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(emailAddress)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
Optional<ContentValues> picture = ContactFactory.getValuesForPhoto(vCard.getComponent());
if (picture.isPresent()) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(picture.get())
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
ContactFactory.getSizeOfPhotoInBytes(picture.get()));
}
List<ContentValues> organizations = ContactFactory.getValuesForOrganizations(vCard.getComponent());
for (ContentValues organization : organizations) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(organization)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> instantMessaging = ContactFactory.getValuesForInstantMessaging(vCard.getComponent());
for (ContentValues instantMessenger : instantMessaging) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(instantMessenger)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> nickNames = ContactFactory.getValuesForNickNames(vCard.getComponent());
for (ContentValues nickName : nickNames) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(nickName)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> notes = ContactFactory.getValuesForNotes(vCard.getComponent());
for (ContentValues note : notes) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(note)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
ContactFactory.getSizeOfNoteInBytes(note));
}
List<ContentValues> postalAddresses = ContactFactory.getValuesForPostalAddresses(vCard.getComponent());
for (ContentValues postalAddress : postalAddresses) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(postalAddress)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> websites = ContactFactory.getValuesForWebsites(vCard.getComponent());
for (ContentValues website : websites) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(website)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> events = ContactFactory.getValuesForEvents(vCard.getComponent());
for (ContentValues event : events) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(event)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
List<ContentValues> sipAddresses = ContactFactory.getValuesForSipAddresses(vCard.getComponent());
for (ContentValues sipAddress : sipAddresses) {
operationQueue.queue(
ContentProviderOperation.newInsert(getUriForData())
.withValues(sipAddress)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, rawContactOpIndex)
.build(),
256);
}
operationQueue.queueAll(
getOperationsForAggregationExceptions(vCard.getComponent(), rawContactOpIndex),
256
);
return operationQueue.size() - rawContactOpIndex;
}
@Override
public int updateComponent(ComponentETagPair<VCard> vCard)
throws InvalidRemoteComponentException, RemoteException
{
if (vCard.getComponent().getUid() != null) {
removeComponent(vCard.getComponent().getUid().getValue());
return addComponent(vCard);
}
else {
Log.e(TAG, "was given a vcard with missing uid");
throw new InvalidRemoteComponentException("Cannot update a vCard without UID!",
CardDavConstants.CARDDAV_NAMESPACE, getPath());
}
}
private boolean handleCommitPendingIfFull(LocalContactCollection toCollection,
List<Integer> contactOperationCounts,
ContactCopiedListener listener,
boolean forceFull)
{
if (toCollection.operationQueue.hasSpace() && !forceFull)
return false;
try {
int pendingCount = toCollection.operationQueue.size();
int successCount = toCollection.commitPendingOperations();
int failCount = pendingCount - successCount;
Log.d(TAG, pendingCount + " were pending " + successCount + " were committed");
int contactCount = 0;
while (contactCount++ < contactOperationCounts.size())
listener.onContactCopied(account, toCollection.getAccount());
if (failCount > 0)
Log.e(TAG, "failed to commit " + failCount + " " +
"operations but no idea which contacts they're from!");
} catch (OperationApplicationException e) {
int contactCount = 0;
while (contactCount++ < contactOperationCounts.size())
listener.onContactCopyFailed(e, account, toCollection.getAccount());
} catch (RemoteException e) {
int contactCount = 0;
while (contactCount++ < contactOperationCounts.size())
listener.onContactCopyFailed(e, account, toCollection.getAccount());
}
return true;
}
public void copyToAccount(Account toAccount, ContactCopiedListener listener)
throws RemoteException
{
LocalContactCollection toCollection = new LocalContactCollection(context, client, toAccount, getPath());
List<Long> componentIds = getComponentIds();
List<Integer> contactOperationCounts = new LinkedList<Integer>();
Log.d(TAG, "copy my " + componentIds.size() + " contacts to " + toAccount.name);
for (Long contactId : componentIds) {
try {
Optional<VCard> copyContact = getComponent(contactId);
if (copyContact.isPresent()) {
if (!ContactFactory.hasInvisibleProperty(copyContact.get())) {
copyContact.get().setUid(null);
ComponentETagPair<VCard> correctedContact =
new ComponentETagPair<VCard>(copyContact.get(), Optional.<String>absent());
contactOperationCounts.add(toCollection.addComponent(correctedContact));
if (handleCommitPendingIfFull(toCollection, contactOperationCounts, listener, false))
contactOperationCounts.clear();
}
else {
Log.w(TAG, "contact is entirely invisible to the user, ignoring.");
listener.onContactCopied(account, toAccount);
}
}
else
throw new InvalidLocalComponentException("absent component for local id on copy",
CardDavConstants.CARDDAV_NAMESPACE, getPath(), contactId);
} catch (InvalidLocalComponentException e) {
listener.onContactCopyFailed(e, getAccount(), toAccount);
}
}
if (toCollection.operationQueue.size() > 0)
handleCommitPendingIfFull(toCollection, contactOperationCounts, listener, true);
}
}