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:
parent
be833b03ee
commit
f1ea00d816
@ -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') {
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)))
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user