|
|
/*
|
|
|
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
|
|
* All rights reserved. This program and the accompanying materials
|
|
|
* are made available under the terms of the GNU Public License v3.0
|
|
|
* which accompanies this distribution, and is available at
|
|
|
* http://www.gnu.org/licenses/gpl.html
|
|
|
*/
|
|
|
|
|
|
package at.bitfire.davdroid.resource;
|
|
|
|
|
|
import android.content.ContentProviderOperation;
|
|
|
import android.content.ContentValues;
|
|
|
import android.os.RemoteException;
|
|
|
import android.provider.ContactsContract;
|
|
|
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
|
|
|
import android.provider.ContactsContract.RawContacts.Data;
|
|
|
import android.support.annotation.NonNull;
|
|
|
import android.text.TextUtils;
|
|
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
import java.io.FileNotFoundException;
|
|
|
import java.io.IOException;
|
|
|
import java.util.HashSet;
|
|
|
import java.util.Set;
|
|
|
import java.util.logging.Level;
|
|
|
|
|
|
import at.bitfire.davdroid.App;
|
|
|
import at.bitfire.davdroid.model.UnknownProperties;
|
|
|
import at.bitfire.vcard4android.AndroidAddressBook;
|
|
|
import at.bitfire.vcard4android.AndroidContact;
|
|
|
import at.bitfire.vcard4android.AndroidContactFactory;
|
|
|
import at.bitfire.vcard4android.BatchOperation;
|
|
|
import at.bitfire.vcard4android.CachedGroupMembership;
|
|
|
import at.bitfire.vcard4android.Contact;
|
|
|
import at.bitfire.vcard4android.ContactsStorageException;
|
|
|
import ezvcard.VCardVersion;
|
|
|
|
|
|
import static at.bitfire.vcard4android.GroupMethod.GROUP_VCARDS;
|
|
|
|
|
|
public class LocalContact extends AndroidContact implements LocalResource {
|
|
|
protected final Set<Long>
|
|
|
cachedGroupMemberships = new HashSet<>(),
|
|
|
groupMemberships = new HashSet<>();
|
|
|
|
|
|
|
|
|
protected LocalContact(AndroidAddressBook addressBook, long id, String uuid, String eTag) {
|
|
|
super(addressBook, id, uuid, eTag);
|
|
|
}
|
|
|
|
|
|
public LocalContact(AndroidAddressBook addressBook, Contact contact, String uuid, String eTag) {
|
|
|
super(addressBook, contact, uuid, eTag);
|
|
|
}
|
|
|
|
|
|
public String getUuid() {
|
|
|
// The same now
|
|
|
return getFileName();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public boolean isLocalOnly() {
|
|
|
return TextUtils.isEmpty(getETag());
|
|
|
}
|
|
|
|
|
|
public void clearDirty(String eTag) throws ContactsStorageException {
|
|
|
try {
|
|
|
ContentValues values = new ContentValues(1);
|
|
|
values.put(ContactsContract.RawContacts.DIRTY, 0);
|
|
|
values.put(COLUMN_ETAG, eTag);
|
|
|
addressBook.provider.update(rawContactSyncURI(), values, null, null);
|
|
|
|
|
|
this.eTag = eTag;
|
|
|
} catch (RemoteException e) {
|
|
|
throw new ContactsStorageException("Couldn't clear dirty flag", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public void updateFileNameAndUID(String uid) throws ContactsStorageException {
|
|
|
try {
|
|
|
String newFileName = uid;
|
|
|
|
|
|
ContentValues values = new ContentValues(2);
|
|
|
values.put(COLUMN_FILENAME, newFileName);
|
|
|
values.put(COLUMN_UID, uid);
|
|
|
addressBook.provider.update(rawContactSyncURI(), values, null, null);
|
|
|
|
|
|
fileName = newFileName;
|
|
|
} catch (RemoteException e) {
|
|
|
throw new ContactsStorageException("Couldn't update UID", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public String getContent() throws IOException, ContactsStorageException {
|
|
|
final Contact contact;
|
|
|
contact = getContact();
|
|
|
|
|
|
App.log.log(Level.FINE, "Preparing upload of VCard " + getUuid(), contact);
|
|
|
|
|
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
|
contact.write(VCardVersion.V4_0, GROUP_VCARDS, os);
|
|
|
|
|
|
return os.toString();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
protected void populateData(String mimeType, ContentValues row) {
|
|
|
switch (mimeType) {
|
|
|
case CachedGroupMembership.CONTENT_ITEM_TYPE:
|
|
|
cachedGroupMemberships.add(row.getAsLong(CachedGroupMembership.GROUP_ID));
|
|
|
break;
|
|
|
case GroupMembership.CONTENT_ITEM_TYPE:
|
|
|
groupMemberships.add(row.getAsLong(GroupMembership.GROUP_ROW_ID));
|
|
|
break;
|
|
|
case UnknownProperties.CONTENT_ITEM_TYPE:
|
|
|
contact.unknownProperties = row.getAsString(UnknownProperties.UNKNOWN_PROPERTIES);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
protected void insertDataRows(BatchOperation batch) throws ContactsStorageException {
|
|
|
super.insertDataRows(batch);
|
|
|
|
|
|
if (contact.unknownProperties != null) {
|
|
|
final BatchOperation.Operation op;
|
|
|
final ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(dataSyncURI());
|
|
|
if (id == null) {
|
|
|
op = new BatchOperation.Operation(builder, UnknownProperties.RAW_CONTACT_ID, 0);
|
|
|
} else {
|
|
|
op = new BatchOperation.Operation(builder);
|
|
|
builder.withValue(UnknownProperties.RAW_CONTACT_ID, id);
|
|
|
}
|
|
|
builder .withValue(UnknownProperties.MIMETYPE, UnknownProperties.CONTENT_ITEM_TYPE)
|
|
|
.withValue(UnknownProperties.UNKNOWN_PROPERTIES, contact.unknownProperties);
|
|
|
batch.enqueue(op);
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
public void addToGroup(BatchOperation batch, long groupID) {
|
|
|
assertID();
|
|
|
batch.enqueue(new BatchOperation.Operation(
|
|
|
ContentProviderOperation.newInsert(dataSyncURI())
|
|
|
.withValue(GroupMembership.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE)
|
|
|
.withValue(GroupMembership.RAW_CONTACT_ID, id)
|
|
|
.withValue(GroupMembership.GROUP_ROW_ID, groupID)
|
|
|
));
|
|
|
|
|
|
batch.enqueue(new BatchOperation.Operation(
|
|
|
ContentProviderOperation.newInsert(dataSyncURI())
|
|
|
.withValue(CachedGroupMembership.MIMETYPE, CachedGroupMembership.CONTENT_ITEM_TYPE)
|
|
|
.withValue(CachedGroupMembership.RAW_CONTACT_ID, id)
|
|
|
.withValue(CachedGroupMembership.GROUP_ID, groupID)
|
|
|
.withYieldAllowed(true)
|
|
|
));
|
|
|
}
|
|
|
|
|
|
public void removeGroupMemberships(BatchOperation batch) {
|
|
|
assertID();
|
|
|
batch.enqueue(new BatchOperation.Operation(
|
|
|
ContentProviderOperation.newDelete(dataSyncURI())
|
|
|
.withSelection(
|
|
|
Data.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + " IN (?,?)",
|
|
|
new String[] { String.valueOf(id), GroupMembership.CONTENT_ITEM_TYPE, CachedGroupMembership.CONTENT_ITEM_TYPE }
|
|
|
)
|
|
|
.withYieldAllowed(true)
|
|
|
));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Returns the IDs of all groups the contact was member of (cached memberships).
|
|
|
* Cached memberships are kept in sync with memberships by DAVdroid and are used to determine
|
|
|
* whether a membership has been deleted/added when a raw contact is dirty.
|
|
|
* @return set of {@link GroupMembership#GROUP_ROW_ID} (may be empty)
|
|
|
* @throws ContactsStorageException on contact provider errors
|
|
|
* @throws FileNotFoundException if the current contact can't be found
|
|
|
*/
|
|
|
@NonNull
|
|
|
public Set<Long> getCachedGroupMemberships() throws ContactsStorageException, FileNotFoundException {
|
|
|
getContact();
|
|
|
return cachedGroupMemberships;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Returns the IDs of all groups the contact is member of.
|
|
|
* @return set of {@link GroupMembership#GROUP_ROW_ID}s (may be empty)
|
|
|
* @throws ContactsStorageException on contact provider errors
|
|
|
* @throws FileNotFoundException if the current contact can't be found
|
|
|
*/
|
|
|
@NonNull
|
|
|
public Set<Long> getGroupMemberships() throws ContactsStorageException, FileNotFoundException {
|
|
|
getContact();
|
|
|
return groupMemberships;
|
|
|
}
|
|
|
|
|
|
|
|
|
// factory
|
|
|
|
|
|
static class Factory extends AndroidContactFactory {
|
|
|
static final Factory INSTANCE = new Factory();
|
|
|
|
|
|
@Override
|
|
|
public LocalContact newInstance(AndroidAddressBook addressBook, long id, String fileName, String eTag) {
|
|
|
return new LocalContact(addressBook, id, fileName, eTag);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public LocalContact[] newArray(int size) {
|
|
|
return new LocalContact[size];
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|