diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 1b2b8ead..90d580d1 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="24" + android:versionName="0.5.6-alpha" > - + - + @@ -66,17 +62,11 @@ - + - + - + @@ -85,13 +75,8 @@ - - + + diff --git a/src/at/bitfire/davdroid/Constants.java b/src/at/bitfire/davdroid/Constants.java index 55003fec..190e6f2b 100644 --- a/src/at/bitfire/davdroid/Constants.java +++ b/src/at/bitfire/davdroid/Constants.java @@ -12,7 +12,7 @@ package at.bitfire.davdroid; public class Constants { public static final String - APP_VERSION = "0.5.5-alpha", + APP_VERSION = "0.5.6-alpha", ACCOUNT_TYPE = "bitfire.at.davdroid", diff --git a/src/at/bitfire/davdroid/URIUtils.java b/src/at/bitfire/davdroid/URIUtils.java index 34fde76e..679d4c24 100644 --- a/src/at/bitfire/davdroid/URIUtils.java +++ b/src/at/bitfire/davdroid/URIUtils.java @@ -23,6 +23,9 @@ public class URIUtils { // handles invalid URLs/paths as good as possible public static String sanitize(String original) { + if (original == null) + return null; + Pattern p = Pattern.compile("^((https?:)?//([^/]+))?(.*)", Pattern.CASE_INSENSITIVE); // $1: "http://hostname" or "https://hostname" or "//hostname" or empty (hostname may end with":port") // $2: "http:" or "https:" or empty @@ -30,7 +33,7 @@ public class URIUtils { // $4: path or empty Matcher m = p.matcher(original); - if (m.find()) { + if (m.matches()) { String schema = m.group(2), host = m.group(3), path = m.group(4); diff --git a/src/at/bitfire/davdroid/resource/LocalAddressBook.java b/src/at/bitfire/davdroid/resource/LocalAddressBook.java index c0e75a16..e2e95636 100644 --- a/src/at/bitfire/davdroid/resource/LocalAddressBook.java +++ b/src/at/bitfire/davdroid/resource/LocalAddressBook.java @@ -193,7 +193,7 @@ public class LocalAddressBook extends LocalCollection { } 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 + "=?", new String[] { String.valueOf(c.getLocalID()), Phone.CONTENT_ITEM_TYPE }, null); while (cursor != null && cursor.moveToNext()) { @@ -265,12 +265,14 @@ public class LocalAddressBook extends LocalCollection { if (!StringUtils.isEmpty(customType)) number.addType(TelephoneType.get(labelToXName(customType))); } + if (cursor.getInt(3) != 0) // IS_PRIMARY + number.addType(TelephoneType.PREF); c.getPhoneNumbers().add(number); } } 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 + "=?", new String[] { String.valueOf(c.getLocalID()), Email.CONTENT_ITEM_TYPE }, null); while (cursor != null && cursor.moveToNext()) { @@ -290,6 +292,8 @@ public class LocalAddressBook extends LocalCollection { if (!StringUtils.isEmpty(customType)) email.addType(EmailType.get(labelToXName(customType))); } + if (cursor.getInt(3) != 0) // IS_PRIMARY + email.addType(EmailType.PREF); c.getEmails().add(email); } } @@ -572,7 +576,13 @@ public class LocalAddressBook extends LocalCollection { protected Builder buildPhoneNumber(Builder builder, Telephone number) { int typeCode = Phone.TYPE_OTHER; String typeLabel = null; + boolean is_primary = false; + Set types = number.getTypes(); + // preferred number? + if (types.contains(TelephoneType.PREF)) + is_primary = true; + // 1 Android type <-> 2 VCard types: fax, cell, pager if (types.contains(TelephoneType.FAX)) { if (types.contains(TelephoneType.HOME)) @@ -625,7 +635,9 @@ public class LocalAddressBook extends LocalCollection { builder = builder .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE) .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) builder = builder.withValue(Phone.LABEL, typeLabel); return builder; @@ -634,9 +646,12 @@ public class LocalAddressBook extends LocalCollection { protected Builder buildEmail(Builder builder, ezvcard.property.Email email) { int typeCode = 0; String typeLabel = null; + boolean is_primary = false; 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; else if (type == EmailType.WORK) typeCode = Email.TYPE_WORK; @@ -654,7 +669,9 @@ public class LocalAddressBook extends LocalCollection { builder = builder .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE) .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) builder = builder.withValue(Email.LABEL, typeLabel); return builder; diff --git a/src/at/bitfire/davdroid/resource/LocalCalendar.java b/src/at/bitfire/davdroid/resource/LocalCalendar.java index 9ffe5b79..6ae9c038 100644 --- a/src/at/bitfire/davdroid/resource/LocalCalendar.java +++ b/src/at/bitfire/davdroid/resource/LocalCalendar.java @@ -95,7 +95,7 @@ public class LocalCalendar extends LocalCollection { @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) 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; } @@ -124,8 +124,6 @@ public class LocalCalendar extends LocalCollection { values.put(Calendars.CALENDAR_DISPLAY_NAME, info.getTitle()); values.put(Calendars.CALENDAR_COLOR, color); 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.CAN_ORGANIZER_RESPOND, 1); values.put(Calendars.CAN_MODIFY_TIME_ZONE, 1); @@ -133,6 +131,11 @@ public class LocalCalendar extends LocalCollection { values.put(Calendars.SYNC_EVENTS, 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) values.put(Calendars.CALENDAR_TIME_ZONE, info.getTimezone()); diff --git a/src/at/bitfire/davdroid/resource/LocalCollection.java b/src/at/bitfire/davdroid/resource/LocalCollection.java index ad58586f..e4da862b 100644 --- a/src/at/bitfire/davdroid/resource/LocalCollection.java +++ b/src/at/bitfire/davdroid/resource/LocalCollection.java @@ -66,9 +66,44 @@ public abstract class LocalCollection { // content provider (= database) querying + + 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 long[] findDirty() throws LocalStorageException { - String where = entryColumnDirty() + "=1"; + 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) where += " AND " + entryColumnParentID() + "=" + String.valueOf(getId()); try { @@ -106,40 +141,6 @@ public abstract class LocalCollection { throw new LocalStorageException(ex); } } - - 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 { try { diff --git a/src/at/bitfire/davdroid/syncadapter/SyncManager.java b/src/at/bitfire/davdroid/syncadapter/SyncManager.java index 4ec8e728..30ae59f3 100644 --- a/src/at/bitfire/davdroid/syncadapter/SyncManager.java +++ b/src/at/bitfire/davdroid/syncadapter/SyncManager.java @@ -146,7 +146,7 @@ public class SyncManager { private int pushDirty() throws LocalStorageException, IOException, HttpException { int count = 0; - long[] dirtyIDs = local.findDirty(); + long[] dirtyIDs = local.findUpdated(); Log.i(TAG, "Uploading " + dirtyIDs.length + " modified resource(s) (if not changed)"); try { for (long id : dirtyIDs) { diff --git a/test/src/at/bitfire/davdroid/test/URIUtilsTest.java b/test/src/at/bitfire/davdroid/test/URIUtilsTest.java index 28e5d43f..bf1c976d 100644 --- a/test/src/at/bitfire/davdroid/test/URIUtilsTest.java +++ b/test/src/at/bitfire/davdroid/test/URIUtilsTest.java @@ -6,6 +6,8 @@ import at.bitfire.davdroid.URIUtils; public class URIUtilsTest extends TestCase { public void testSanitize() { + assertNull(URIUtils.sanitize(null)); + // escape "@" 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")); diff --git a/test/src/at/bitfire/davdroid/webdav/test/WebDavResourceTest.java b/test/src/at/bitfire/davdroid/webdav/test/WebDavResourceTest.java index 8e1e38f3..6c16a73b 100644 --- a/test/src/at/bitfire/davdroid/webdav/test/WebDavResourceTest.java +++ b/test/src/at/bitfire/davdroid/webdav/test/WebDavResourceTest.java @@ -206,7 +206,7 @@ public class WebDavResourceTest extends InstrumentationTestCase { dav.propfind(HttpPropfind.Mode.MEMBERS_COLLECTIONS); List members = dav.getMembers(); 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()); }