1
0
mirror of https://github.com/etesync/android synced 2025-01-11 08:10:58 +00:00

Improve Android 7 workaround behavior in combination with CATEGORIES/VCard4 contact groups

This commit is contained in:
Ricki Hirner 2017-02-12 18:57:45 +01:00 committed by Tom Hacohen
parent be833b03ee
commit f1ea00d816
5 changed files with 82 additions and 27 deletions

View File

@ -135,7 +135,7 @@ dependencies {
compile group: 'com.madgag.spongycastle', name: 'prov', version: '1.54.0.0'
compile group: 'com.google.code.gson', name: 'gson', version: '1.7.2'
compile 'com.squareup.okhttp3:logging-interceptor:3.6.0'
provided 'org.projectlombok:lombok:1.16.12'
provided 'org.projectlombok:lombok:1.16.14'
// for tests
androidTestCompile('com.android.support.test:runner:0.5') {

View File

@ -19,6 +19,7 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
import android.support.annotation.NonNull;
@ -27,13 +28,16 @@ import com.etesync.syncadapter.App;
import java.io.FileNotFoundException;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import at.bitfire.vcard4android.AndroidAddressBook;
import at.bitfire.vcard4android.AndroidContact;
import at.bitfire.vcard4android.AndroidGroup;
import at.bitfire.vcard4android.CachedGroupMembership;
import at.bitfire.vcard4android.ContactsStorageException;
import lombok.Cleanup;
@ -149,15 +153,6 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
return null;
}
public void deleteAll() throws ContactsStorageException {
try {
provider.delete(syncAdapterURI(RawContacts.CONTENT_URI), null, null);
provider.delete(syncAdapterURI(Groups.CONTENT_URI), null, null);
} catch(RemoteException e) {
throw new ContactsStorageException("Couldn't delete all local contacts and groups", e);
}
}
@Override
public long count() throws ContactsStorageException {
try {
@ -196,6 +191,38 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
return (LocalGroup[])queryGroups(Groups.DIRTY + "!= 0 AND " + Groups.DELETED + "== 0", null);
}
@NonNull LocalContact[] getByGroupMembership(long groupID) throws ContactsStorageException {
try {
@Cleanup Cursor cursor = provider.query(syncAdapterURI(ContactsContract.Data.CONTENT_URI),
new String[] { RawContacts.Data.RAW_CONTACT_ID },
"(" + GroupMembership.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?) OR (" + CachedGroupMembership.MIMETYPE + "=? AND " + CachedGroupMembership.GROUP_ID + "=?)",
new String[] { GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupID), CachedGroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupID) },
null);
Set<Long> ids = new HashSet<>();
while (cursor != null && cursor.moveToNext())
ids.add(cursor.getLong(0));
LocalContact[] contacts = new LocalContact[ids.size()];
int i = 0;
for (Long id : ids)
contacts[i++] = new LocalContact(this, id, null, null);
return contacts;
} catch (RemoteException e) {
throw new ContactsStorageException("Couldn't query contacts", e);
}
}
public void deleteAll() throws ContactsStorageException {
try {
provider.delete(syncAdapterURI(RawContacts.CONTENT_URI), null, null);
provider.delete(syncAdapterURI(Groups.CONTENT_URI), null, null);
} catch(RemoteException e) {
throw new ContactsStorageException("Couldn't delete all local contacts and groups", e);
}
}
/**
* Finds the first group with the given title. If there is no group with this

View File

@ -18,6 +18,7 @@ import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.RawContacts.Data;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.etesync.syncadapter.App;
@ -184,30 +185,41 @@ public class LocalContact extends AndroidContact implements LocalResource {
/**
* Calculates a hash code from the contact's data (VCard) and group memberships.
* Attention: re-reads {@link #contact} from the database, discarding all changes in memory
* @return hash code of contact data (including group memberships)
*/
protected int dataHashCode() throws FileNotFoundException, ContactsStorageException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
App.log.severe("dataHashCode() should not be called on Android <7");
// reset contact so that getContact() reads from database
contact = null;
// groupMemberships is filled by getContact()
return getContact().hashCode() ^ groupMemberships.hashCode();
int dataHash = getContact().hashCode(),
groupHash = groupMemberships.hashCode();
App.log.finest("Calculated data hash = " + dataHash + ", group memberships hash = " + groupHash);
return dataHash ^ groupHash;
}
public void updateHashCode() throws ContactsStorageException {
public void updateHashCode(@Nullable BatchOperation batch) throws ContactsStorageException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
App.log.severe("updateHashCode() should not be called on Android <7");
// re-read from provider before calculating the hash because the Contact as parsed from
// the VCard is not the same as when read from the database
contact = null;
ContentValues values = new ContentValues(1);
try {
int hashCode = dataHashCode();
App.log.fine("Storing contact hash = " + hashCode);
values.put(COLUMN_HASHCODE, hashCode);
addressBook.provider.update(rawContactSyncURI(), values, null, null);
if (batch == null)
addressBook.provider.update(rawContactSyncURI(), values, null, null);
else {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(rawContactSyncURI())
.withValues(values);
batch.enqueue(new BatchOperation.Operation(builder));
}
} catch(FileNotFoundException|RemoteException e) {
throw new ContactsStorageException("Couldn't store contact checksum", e);
}
@ -230,12 +242,14 @@ public class LocalContact extends AndroidContact implements LocalResource {
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)
));
groupMemberships.add(groupID);
batch.enqueue(new BatchOperation.Operation(
ContentProviderOperation.newInsert(dataSyncURI())
@ -244,6 +258,7 @@ public class LocalContact extends AndroidContact implements LocalResource {
.withValue(CachedGroupMembership.GROUP_ID, groupID)
.withYieldAllowed(true)
));
cachedGroupMemberships.add(groupID);
}
public void removeGroupMemberships(BatchOperation batch) {
@ -256,6 +271,8 @@ public class LocalContact extends AndroidContact implements LocalResource {
)
.withYieldAllowed(true)
));
groupMemberships.clear();
cachedGroupMemberships.clear();
}
/**

View File

@ -12,6 +12,7 @@ import android.content.ContentProviderOperation;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Build;
import android.os.Parcel;
import android.os.RemoteException;
import android.provider.ContactsContract;
@ -20,16 +21,19 @@ import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContacts.Data;
import com.etesync.syncadapter.App;
import org.apache.commons.lang3.ArrayUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import com.etesync.syncadapter.App;
import at.bitfire.vcard4android.AndroidAddressBook;
import at.bitfire.vcard4android.AndroidGroup;
import at.bitfire.vcard4android.AndroidGroupFactory;
@ -162,15 +166,14 @@ public class LocalGroup extends AndroidGroup implements LocalResource {
long id = cursor.getLong(0);
App.log.fine("Assigning members to group " + id);
// required for workaround for Android 7 which sets DIRTY flag when only meta-data is changed
Set<Long> changeContactIDs = new HashSet<>();
// delete all memberships and cached memberships for this group
batch.enqueue(new BatchOperation.Operation(
ContentProviderOperation.newDelete(addressBook.syncAdapterURI(ContactsContract.Data.CONTENT_URI))
.withSelection(
"(" + GroupMembership.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?) OR (" +
CachedGroupMembership.MIMETYPE + "=? AND " + CachedGroupMembership.GROUP_ID + "=?)",
new String[] { GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(id), CachedGroupMembership.CONTENT_ITEM_TYPE, String.valueOf(id) })
.withYieldAllowed(true)
));
for (LocalContact contact : addressBook.getByGroupMembership(id)) {
contact.removeGroupMemberships(batch);
changeContactIDs.add(contact.getId());
}
// extract list of member UIDs
List<String> members = new LinkedList<>();
@ -186,11 +189,19 @@ public class LocalGroup extends AndroidGroup implements LocalResource {
try {
LocalContact member = addressBook.findContactByUID(uid);
member.addToGroup(batch, id);
changeContactIDs.add(member.getId());
} catch(FileNotFoundException e) {
App.log.log(Level.WARNING, "Group member not found: " + uid, e);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
// workaround for Android 7 which sets DIRTY flag when only meta-data is changed
for (Long contactID : changeContactIDs) {
LocalContact contact = new LocalContact(addressBook, contactID, null, null);
contact.updateHashCode(batch);
}
// remove pending memberships
batch.enqueue(new BatchOperation.Operation(
ContentProviderOperation.newUpdate(addressBook.syncAdapterURI(ContentUris.withAppendedId(Groups.CONTENT_URI, id)))

View File

@ -208,7 +208,7 @@ public class ContactsSyncManager extends SyncManager {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && local instanceof LocalContact)
// workaround for Android 7 which sets DIRTY flag when only meta-data is changed
((LocalContact)local).updateHashCode();
((LocalContact)local).updateHashCode(null);
return local;
}