1
0
mirror of https://github.com/etesync/android synced 2024-11-26 18:08:11 +00:00

0.5.6α Wage Slave

* support primary (preferred) phone numbers/email addresses
* fix (crash) bug in URI sanitation
* don't set calendar properties not supported by device's Android version
* require already-set remote file name when finding updated records
* version bump to 0.5.6α
This commit is contained in:
rfc2822 2014-01-06 20:01:03 +01:00
parent 4dc65d0144
commit 4f1488ece5
10 changed files with 83 additions and 72 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="at.bitfire.davdroid" package="at.bitfire.davdroid"
android:versionCode="23" android:versionCode="24"
android:versionName="0.5.5-alpha" > android:versionName="0.5.6-alpha" >
<uses-sdk <uses-sdk
android:minSdkVersion="14" android:minSdkVersion="14"

View File

@ -46,13 +46,9 @@
<Type type="custom" /> <Type type="custom" />
</DataKind> </DataKind>
<DataKind <DataKind kind="photo" maxOccurs="1" />
kind="photo"
maxOccurs="1" />
<DataKind <DataKind kind="organization" maxOccurs="1" />
kind="organization"
maxOccurs="1" />
<DataKind kind="im" > <DataKind kind="im" >
<Type type="aim" /> <Type type="aim" />
@ -66,17 +62,11 @@
<Type type="custom" /> <Type type="custom" />
</DataKind> </DataKind>
<DataKind <DataKind kind="nickname" maxOccurs="1" />
kind="nickname"
maxOccurs="1" />
<DataKind <DataKind kind="note" maxOccurs="1" />
kind="note"
maxOccurs="1" />
<DataKind <DataKind kind="postal" needsStructured="true" >
kind="postal"
needsStructured="true" >
<Type type="home" /> <Type type="home" />
<Type type="work" /> <Type type="work" />
<Type type="other" /> <Type type="other" />
@ -85,13 +75,8 @@
<DataKind kind="website" /> <DataKind kind="website" />
<DataKind <DataKind kind="event" dateWithTime="false" >
dateWithTime="false" <Type maxOccurs="1" type="birthday" yearOptional="false" />
kind="event" >
<Type
maxOccurs="1"
type="birthday"
yearOptional="false" />
<Type type="anniversary" /> <Type type="anniversary" />
</DataKind> </DataKind>

View File

@ -12,7 +12,7 @@ package at.bitfire.davdroid;
public class Constants { public class Constants {
public static final String public static final String
APP_VERSION = "0.5.5-alpha", APP_VERSION = "0.5.6-alpha",
ACCOUNT_TYPE = "bitfire.at.davdroid", ACCOUNT_TYPE = "bitfire.at.davdroid",

View File

@ -23,6 +23,9 @@ public class URIUtils {
// handles invalid URLs/paths as good as possible // handles invalid URLs/paths as good as possible
public static String sanitize(String original) { public static String sanitize(String original) {
if (original == null)
return null;
Pattern p = Pattern.compile("^((https?:)?//([^/]+))?(.*)", Pattern.CASE_INSENSITIVE); Pattern p = Pattern.compile("^((https?:)?//([^/]+))?(.*)", Pattern.CASE_INSENSITIVE);
// $1: "http://hostname" or "https://hostname" or "//hostname" or empty (hostname may end with":port") // $1: "http://hostname" or "https://hostname" or "//hostname" or empty (hostname may end with":port")
// $2: "http:" or "https:" or empty // $2: "http:" or "https:" or empty
@ -30,7 +33,7 @@ public class URIUtils {
// $4: path or empty // $4: path or empty
Matcher m = p.matcher(original); Matcher m = p.matcher(original);
if (m.find()) { if (m.matches()) {
String schema = m.group(2), String schema = m.group(2),
host = m.group(3), host = m.group(3),
path = m.group(4); path = m.group(4);

View File

@ -193,7 +193,7 @@ public class LocalAddressBook extends LocalCollection<Contact> {
} }
protected void populatePhoneNumbers(Contact c) throws RemoteException { protected void populatePhoneNumbers(Contact c) throws RemoteException {
@Cleanup Cursor cursor = providerClient.query(dataURI(), new String[] { Phone.TYPE, Phone.LABEL, Phone.NUMBER }, @Cleanup Cursor cursor = providerClient.query(dataURI(), new String[] { Phone.TYPE, Phone.LABEL, Phone.NUMBER, Phone.IS_SUPER_PRIMARY },
Phone.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", Phone.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?",
new String[] { String.valueOf(c.getLocalID()), Phone.CONTENT_ITEM_TYPE }, null); new String[] { String.valueOf(c.getLocalID()), Phone.CONTENT_ITEM_TYPE }, null);
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
@ -265,12 +265,14 @@ public class LocalAddressBook extends LocalCollection<Contact> {
if (!StringUtils.isEmpty(customType)) if (!StringUtils.isEmpty(customType))
number.addType(TelephoneType.get(labelToXName(customType))); number.addType(TelephoneType.get(labelToXName(customType)));
} }
if (cursor.getInt(3) != 0) // IS_PRIMARY
number.addType(TelephoneType.PREF);
c.getPhoneNumbers().add(number); c.getPhoneNumbers().add(number);
} }
} }
protected void populateEmailAddresses(Contact c) throws RemoteException { protected void populateEmailAddresses(Contact c) throws RemoteException {
@Cleanup Cursor cursor = providerClient.query(dataURI(), new String[] { Email.TYPE, Email.ADDRESS, Email.LABEL }, @Cleanup Cursor cursor = providerClient.query(dataURI(), new String[] { Email.TYPE, Email.ADDRESS, Email.LABEL, Email.IS_SUPER_PRIMARY },
Email.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", Email.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?",
new String[] { String.valueOf(c.getLocalID()), Email.CONTENT_ITEM_TYPE }, null); new String[] { String.valueOf(c.getLocalID()), Email.CONTENT_ITEM_TYPE }, null);
while (cursor != null && cursor.moveToNext()) { while (cursor != null && cursor.moveToNext()) {
@ -290,6 +292,8 @@ public class LocalAddressBook extends LocalCollection<Contact> {
if (!StringUtils.isEmpty(customType)) if (!StringUtils.isEmpty(customType))
email.addType(EmailType.get(labelToXName(customType))); email.addType(EmailType.get(labelToXName(customType)));
} }
if (cursor.getInt(3) != 0) // IS_PRIMARY
email.addType(EmailType.PREF);
c.getEmails().add(email); c.getEmails().add(email);
} }
} }
@ -572,7 +576,13 @@ public class LocalAddressBook extends LocalCollection<Contact> {
protected Builder buildPhoneNumber(Builder builder, Telephone number) { protected Builder buildPhoneNumber(Builder builder, Telephone number) {
int typeCode = Phone.TYPE_OTHER; int typeCode = Phone.TYPE_OTHER;
String typeLabel = null; String typeLabel = null;
boolean is_primary = false;
Set<TelephoneType> types = number.getTypes(); Set<TelephoneType> types = number.getTypes();
// preferred number?
if (types.contains(TelephoneType.PREF))
is_primary = true;
// 1 Android type <-> 2 VCard types: fax, cell, pager // 1 Android type <-> 2 VCard types: fax, cell, pager
if (types.contains(TelephoneType.FAX)) { if (types.contains(TelephoneType.FAX)) {
if (types.contains(TelephoneType.HOME)) if (types.contains(TelephoneType.HOME))
@ -625,7 +635,9 @@ public class LocalAddressBook extends LocalCollection<Contact> {
builder = builder builder = builder
.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE) .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
.withValue(Phone.NUMBER, number.getText()) .withValue(Phone.NUMBER, number.getText())
.withValue(Phone.TYPE, typeCode); .withValue(Phone.TYPE, typeCode)
.withValue(Phone.IS_PRIMARY, is_primary ? 1 : 0)
.withValue(Phone.IS_SUPER_PRIMARY, is_primary ? 1 : 0);
if (typeLabel != null) if (typeLabel != null)
builder = builder.withValue(Phone.LABEL, typeLabel); builder = builder.withValue(Phone.LABEL, typeLabel);
return builder; return builder;
@ -634,9 +646,12 @@ public class LocalAddressBook extends LocalCollection<Contact> {
protected Builder buildEmail(Builder builder, ezvcard.property.Email email) { protected Builder buildEmail(Builder builder, ezvcard.property.Email email) {
int typeCode = 0; int typeCode = 0;
String typeLabel = null; String typeLabel = null;
boolean is_primary = false;
for (EmailType type : email.getTypes()) for (EmailType type : email.getTypes())
if (type == EmailType.HOME) if (type == EmailType.PREF)
is_primary = true;
else if (type == EmailType.HOME)
typeCode = Email.TYPE_HOME; typeCode = Email.TYPE_HOME;
else if (type == EmailType.WORK) else if (type == EmailType.WORK)
typeCode = Email.TYPE_WORK; typeCode = Email.TYPE_WORK;
@ -654,7 +669,9 @@ public class LocalAddressBook extends LocalCollection<Contact> {
builder = builder builder = builder
.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE) .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
.withValue(Email.ADDRESS, email.getValue()) .withValue(Email.ADDRESS, email.getValue())
.withValue(Email.TYPE, typeCode); .withValue(Email.TYPE, typeCode)
.withValue(Email.IS_PRIMARY, is_primary ? 1 : 0)
.withValue(Phone.IS_SUPER_PRIMARY, is_primary ? 1 : 0);;
if (typeLabel != null) if (typeLabel != null)
builder = builder.withValue(Email.LABEL, typeLabel); builder = builder.withValue(Email.LABEL, typeLabel);
return builder; return builder;

View File

@ -95,7 +95,7 @@ public class LocalCalendar extends LocalCollection<Event> {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
protected String entryColumnUID() { protected String entryColumnUID() {
return (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) ? return (android.os.Build.VERSION.SDK_INT >= 17) ?
Events.UID_2445 : Events.SYNC_DATA2; Events.UID_2445 : Events.SYNC_DATA2;
} }
@ -124,8 +124,6 @@ public class LocalCalendar extends LocalCollection<Event> {
values.put(Calendars.CALENDAR_DISPLAY_NAME, info.getTitle()); values.put(Calendars.CALENDAR_DISPLAY_NAME, info.getTitle());
values.put(Calendars.CALENDAR_COLOR, color); values.put(Calendars.CALENDAR_COLOR, color);
values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER); values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER);
values.put(Calendars.ALLOWED_AVAILABILITY, Events.AVAILABILITY_BUSY + "," + Events.AVAILABILITY_FREE + "," + Events.AVAILABILITY_TENTATIVE);
values.put(Calendars.ALLOWED_ATTENDEE_TYPES, Attendees.TYPE_NONE + "," + Attendees.TYPE_OPTIONAL + "," + Attendees.TYPE_REQUIRED + "," + Attendees.TYPE_RESOURCE);
values.put(Calendars.ALLOWED_REMINDERS, Reminders.METHOD_ALERT); values.put(Calendars.ALLOWED_REMINDERS, Reminders.METHOD_ALERT);
values.put(Calendars.CAN_ORGANIZER_RESPOND, 1); values.put(Calendars.CAN_ORGANIZER_RESPOND, 1);
values.put(Calendars.CAN_MODIFY_TIME_ZONE, 1); values.put(Calendars.CAN_MODIFY_TIME_ZONE, 1);
@ -133,6 +131,11 @@ public class LocalCalendar extends LocalCollection<Event> {
values.put(Calendars.SYNC_EVENTS, 1); values.put(Calendars.SYNC_EVENTS, 1);
values.put(Calendars.VISIBLE, 1); values.put(Calendars.VISIBLE, 1);
if (android.os.Build.VERSION.SDK_INT >= 15) {
values.put(Calendars.ALLOWED_AVAILABILITY, Events.AVAILABILITY_BUSY + "," + Events.AVAILABILITY_FREE + "," + Events.AVAILABILITY_TENTATIVE);
values.put(Calendars.ALLOWED_ATTENDEE_TYPES, Attendees.TYPE_NONE + "," + Attendees.TYPE_OPTIONAL + "," + Attendees.TYPE_REQUIRED + "," + Attendees.TYPE_RESOURCE);
}
if (info.getTimezone() != null) if (info.getTimezone() != null)
values.put(Calendars.CALENDAR_TIME_ZONE, info.getTimezone()); values.put(Calendars.CALENDAR_TIME_ZONE, info.getTimezone());

View File

@ -67,8 +67,43 @@ public abstract class LocalCollection<T extends Resource> {
// content provider (= database) querying // content provider (= database) querying
public long[] findDirty() throws LocalStorageException { public long[] findNew() throws LocalStorageException {
String where = entryColumnDirty() + "=1"; // new records are 1) dirty, and 2) don't have a remote file name yet
String where = entryColumnDirty() + "=1 AND " + entryColumnRemoteName() + " IS NULL";
if (entryColumnParentID() != null)
where += " AND " + entryColumnParentID() + "=" + String.valueOf(getId());
try {
@Cleanup Cursor cursor = providerClient.query(entriesURI(),
new String[] { entryColumnID() },
where, null, null);
if (cursor == null)
throw new LocalStorageException("Couldn't query new records");
long[] fresh = new long[cursor.getCount()];
for (int idx = 0; cursor.moveToNext(); idx++) {
long id = cursor.getLong(0);
// new record: generate UID + remote file name so that we can upload
T resource = findById(id, false);
resource.generateUID();
resource.generateName();
// write generated UID + remote file name into database
ContentValues values = new ContentValues(2);
values.put(entryColumnUID(), resource.getUid());
values.put(entryColumnRemoteName(), resource.getName());
providerClient.update(ContentUris.withAppendedId(entriesURI(), id), values, null, null);
fresh[idx] = id;
}
return fresh;
} catch(RemoteException ex) {
throw new LocalStorageException(ex);
}
}
public long[] findUpdated() throws LocalStorageException {
// updated records are 1) dirty, and 2) already have a remote file name
String where = entryColumnDirty() + "=1 AND " + entryColumnRemoteName() + " IS NOT NULL";
if (entryColumnParentID() != null) if (entryColumnParentID() != null)
where += " AND " + entryColumnParentID() + "=" + String.valueOf(getId()); where += " AND " + entryColumnParentID() + "=" + String.valueOf(getId());
try { try {
@ -107,40 +142,6 @@ public abstract class LocalCollection<T extends Resource> {
} }
} }
public long[] findNew() throws LocalStorageException {
// new records are 1) dirty, and 2) don't have a remote file name yet
String where = entryColumnDirty() + "=1 AND " + entryColumnRemoteName() + " IS NULL";
if (entryColumnParentID() != null)
where += " AND " + entryColumnParentID() + "=" + String.valueOf(getId());
try {
@Cleanup Cursor cursor = providerClient.query(entriesURI(),
new String[] { entryColumnID() },
where, null, null);
if (cursor == null)
throw new LocalStorageException("Couldn't query new records");
long[] fresh = new long[cursor.getCount()];
for (int idx = 0; cursor.moveToNext(); idx++) {
long id = cursor.getLong(0);
// new record: generate UID + remote file name so that we can upload
T resource = findById(id, false);
resource.generateUID();
resource.generateName();
// write generated UID + remote file name into database
ContentValues values = new ContentValues(2);
values.put(entryColumnUID(), resource.getUid());
values.put(entryColumnRemoteName(), resource.getName());
providerClient.update(ContentUris.withAppendedId(entriesURI(), id), values, null, null);
fresh[idx] = id;
}
return fresh;
} catch(RemoteException ex) {
throw new LocalStorageException(ex);
}
}
public T findById(long localID, boolean populate) throws LocalStorageException { public T findById(long localID, boolean populate) throws LocalStorageException {
try { try {
@Cleanup Cursor cursor = providerClient.query(ContentUris.withAppendedId(entriesURI(), localID), @Cleanup Cursor cursor = providerClient.query(ContentUris.withAppendedId(entriesURI(), localID),

View File

@ -146,7 +146,7 @@ public class SyncManager {
private int pushDirty() throws LocalStorageException, IOException, HttpException { private int pushDirty() throws LocalStorageException, IOException, HttpException {
int count = 0; int count = 0;
long[] dirtyIDs = local.findDirty(); long[] dirtyIDs = local.findUpdated();
Log.i(TAG, "Uploading " + dirtyIDs.length + " modified resource(s) (if not changed)"); Log.i(TAG, "Uploading " + dirtyIDs.length + " modified resource(s) (if not changed)");
try { try {
for (long id : dirtyIDs) { for (long id : dirtyIDs) {

View File

@ -6,6 +6,8 @@ import at.bitfire.davdroid.URIUtils;
public class URIUtilsTest extends TestCase { public class URIUtilsTest extends TestCase {
public void testSanitize() { public void testSanitize() {
assertNull(URIUtils.sanitize(null));
// escape "@" // escape "@"
assertEquals("https://my%40server/my%40email.com/dir", URIUtils.sanitize("https://my@server/my@email.com/dir")); assertEquals("https://my%40server/my%40email.com/dir", URIUtils.sanitize("https://my@server/my@email.com/dir"));
assertEquals("http://my%40server/my%40email.com/dir", URIUtils.sanitize("http://my@server/my@email.com/dir")); assertEquals("http://my%40server/my%40email.com/dir", URIUtils.sanitize("http://my@server/my@email.com/dir"));

View File

@ -206,7 +206,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
dav.propfind(HttpPropfind.Mode.MEMBERS_COLLECTIONS); dav.propfind(HttpPropfind.Mode.MEMBERS_COLLECTIONS);
List<WebDavResource> members = dav.getMembers(); List<WebDavResource> members = dav.getMembers();
assertEquals(2, members.size()); assertEquals(2, members.size());
assertEquals(ROBOHYDRA_BASE + "dav/addressbooks/user%40domain/My%20Contacts%3A1.vcf/", members.get(0).getLocation().toString()); assertEquals(ROBOHYDRA_BASE + "dav/addressbooks/user%40domain/My%20Contacts%3a1.vcf/", members.get(0).getLocation().toString());
assertEquals("HTTPS://example.com/user%40domain/absolute-url.vcf", members.get(1).getLocation().toString()); assertEquals("HTTPS://example.com/user%40domain/absolute-url.vcf", members.get(1).getLocation().toString());
} }