mirror of
https://github.com/etesync/android
synced 2025-06-25 09:22:37 +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.madgag.spongycastle', name: 'prov', version: '1.54.0.0'
|
||||||
compile group: 'com.google.code.gson', name: 'gson', version: '1.7.2'
|
compile group: 'com.google.code.gson', name: 'gson', version: '1.7.2'
|
||||||
compile 'com.squareup.okhttp3:logging-interceptor:3.6.0'
|
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
|
// for tests
|
||||||
androidTestCompile('com.android.support.test:runner:0.5') {
|
androidTestCompile('com.android.support.test:runner:0.5') {
|
||||||
|
@ -19,6 +19,7 @@ import android.os.Bundle;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
|
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
|
||||||
import android.provider.ContactsContract.Groups;
|
import android.provider.ContactsContract.Groups;
|
||||||
import android.provider.ContactsContract.RawContacts;
|
import android.provider.ContactsContract.RawContacts;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
@ -27,13 +28,16 @@ import com.etesync.syncadapter.App;
|
|||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import at.bitfire.vcard4android.AndroidAddressBook;
|
import at.bitfire.vcard4android.AndroidAddressBook;
|
||||||
import at.bitfire.vcard4android.AndroidContact;
|
import at.bitfire.vcard4android.AndroidContact;
|
||||||
import at.bitfire.vcard4android.AndroidGroup;
|
import at.bitfire.vcard4android.AndroidGroup;
|
||||||
|
import at.bitfire.vcard4android.CachedGroupMembership;
|
||||||
import at.bitfire.vcard4android.ContactsStorageException;
|
import at.bitfire.vcard4android.ContactsStorageException;
|
||||||
import lombok.Cleanup;
|
import lombok.Cleanup;
|
||||||
|
|
||||||
@ -149,15 +153,6 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
|
|||||||
return null;
|
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
|
@Override
|
||||||
public long count() throws ContactsStorageException {
|
public long count() throws ContactsStorageException {
|
||||||
try {
|
try {
|
||||||
@ -196,6 +191,38 @@ public class LocalAddressBook extends AndroidAddressBook implements LocalCollect
|
|||||||
return (LocalGroup[])queryGroups(Groups.DIRTY + "!= 0 AND " + Groups.DELETED + "== 0", null);
|
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
|
* 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.CommonDataKinds.GroupMembership;
|
||||||
import android.provider.ContactsContract.RawContacts.Data;
|
import android.provider.ContactsContract.RawContacts.Data;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import com.etesync.syncadapter.App;
|
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.
|
* 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)
|
* @return hash code of contact data (including group memberships)
|
||||||
*/
|
*/
|
||||||
protected int dataHashCode() throws FileNotFoundException, ContactsStorageException {
|
protected int dataHashCode() throws FileNotFoundException, ContactsStorageException {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
|
||||||
App.log.severe("dataHashCode() should not be called on Android <7");
|
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()
|
// 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)
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
|
||||||
App.log.severe("updateHashCode() should not be called on Android <7");
|
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);
|
ContentValues values = new ContentValues(1);
|
||||||
try {
|
try {
|
||||||
int hashCode = dataHashCode();
|
int hashCode = dataHashCode();
|
||||||
App.log.fine("Storing contact hash = " + hashCode);
|
App.log.fine("Storing contact hash = " + hashCode);
|
||||||
values.put(COLUMN_HASHCODE, 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) {
|
} catch(FileNotFoundException|RemoteException e) {
|
||||||
throw new ContactsStorageException("Couldn't store contact checksum", 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) {
|
public void addToGroup(BatchOperation batch, long groupID) {
|
||||||
assertID();
|
assertID();
|
||||||
|
|
||||||
batch.enqueue(new BatchOperation.Operation(
|
batch.enqueue(new BatchOperation.Operation(
|
||||||
ContentProviderOperation.newInsert(dataSyncURI())
|
ContentProviderOperation.newInsert(dataSyncURI())
|
||||||
.withValue(GroupMembership.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE)
|
.withValue(GroupMembership.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE)
|
||||||
.withValue(GroupMembership.RAW_CONTACT_ID, id)
|
.withValue(GroupMembership.RAW_CONTACT_ID, id)
|
||||||
.withValue(GroupMembership.GROUP_ROW_ID, groupID)
|
.withValue(GroupMembership.GROUP_ROW_ID, groupID)
|
||||||
));
|
));
|
||||||
|
groupMemberships.add(groupID);
|
||||||
|
|
||||||
batch.enqueue(new BatchOperation.Operation(
|
batch.enqueue(new BatchOperation.Operation(
|
||||||
ContentProviderOperation.newInsert(dataSyncURI())
|
ContentProviderOperation.newInsert(dataSyncURI())
|
||||||
@ -244,6 +258,7 @@ public class LocalContact extends AndroidContact implements LocalResource {
|
|||||||
.withValue(CachedGroupMembership.GROUP_ID, groupID)
|
.withValue(CachedGroupMembership.GROUP_ID, groupID)
|
||||||
.withYieldAllowed(true)
|
.withYieldAllowed(true)
|
||||||
));
|
));
|
||||||
|
cachedGroupMemberships.add(groupID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeGroupMemberships(BatchOperation batch) {
|
public void removeGroupMemberships(BatchOperation batch) {
|
||||||
@ -256,6 +271,8 @@ public class LocalContact extends AndroidContact implements LocalResource {
|
|||||||
)
|
)
|
||||||
.withYieldAllowed(true)
|
.withYieldAllowed(true)
|
||||||
));
|
));
|
||||||
|
groupMemberships.clear();
|
||||||
|
cachedGroupMemberships.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,6 +12,7 @@ import android.content.ContentProviderOperation;
|
|||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
@ -20,16 +21,19 @@ import android.provider.ContactsContract.Groups;
|
|||||||
import android.provider.ContactsContract.RawContacts;
|
import android.provider.ContactsContract.RawContacts;
|
||||||
import android.provider.ContactsContract.RawContacts.Data;
|
import android.provider.ContactsContract.RawContacts.Data;
|
||||||
|
|
||||||
|
import com.etesync.syncadapter.App;
|
||||||
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import com.etesync.syncadapter.App;
|
|
||||||
import at.bitfire.vcard4android.AndroidAddressBook;
|
import at.bitfire.vcard4android.AndroidAddressBook;
|
||||||
import at.bitfire.vcard4android.AndroidGroup;
|
import at.bitfire.vcard4android.AndroidGroup;
|
||||||
import at.bitfire.vcard4android.AndroidGroupFactory;
|
import at.bitfire.vcard4android.AndroidGroupFactory;
|
||||||
@ -162,15 +166,14 @@ public class LocalGroup extends AndroidGroup implements LocalResource {
|
|||||||
long id = cursor.getLong(0);
|
long id = cursor.getLong(0);
|
||||||
App.log.fine("Assigning members to group " + id);
|
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
|
// delete all memberships and cached memberships for this group
|
||||||
batch.enqueue(new BatchOperation.Operation(
|
for (LocalContact contact : addressBook.getByGroupMembership(id)) {
|
||||||
ContentProviderOperation.newDelete(addressBook.syncAdapterURI(ContactsContract.Data.CONTENT_URI))
|
contact.removeGroupMemberships(batch);
|
||||||
.withSelection(
|
changeContactIDs.add(contact.getId());
|
||||||
"(" + 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)
|
|
||||||
));
|
|
||||||
|
|
||||||
// extract list of member UIDs
|
// extract list of member UIDs
|
||||||
List<String> members = new LinkedList<>();
|
List<String> members = new LinkedList<>();
|
||||||
@ -186,11 +189,19 @@ public class LocalGroup extends AndroidGroup implements LocalResource {
|
|||||||
try {
|
try {
|
||||||
LocalContact member = addressBook.findContactByUID(uid);
|
LocalContact member = addressBook.findContactByUID(uid);
|
||||||
member.addToGroup(batch, id);
|
member.addToGroup(batch, id);
|
||||||
|
changeContactIDs.add(member.getId());
|
||||||
} catch(FileNotFoundException e) {
|
} catch(FileNotFoundException e) {
|
||||||
App.log.log(Level.WARNING, "Group member not found: " + uid, 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
|
// remove pending memberships
|
||||||
batch.enqueue(new BatchOperation.Operation(
|
batch.enqueue(new BatchOperation.Operation(
|
||||||
ContentProviderOperation.newUpdate(addressBook.syncAdapterURI(ContentUris.withAppendedId(Groups.CONTENT_URI, id)))
|
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)
|
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
|
// workaround for Android 7 which sets DIRTY flag when only meta-data is changed
|
||||||
((LocalContact)local).updateHashCode();
|
((LocalContact)local).updateHashCode(null);
|
||||||
|
|
||||||
return local;
|
return local;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user