mirror of
https://github.com/etesync/android
synced 2024-11-22 16:08:13 +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:
parent
4dc65d0144
commit
4f1488ece5
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="at.bitfire.davdroid"
|
||||
android:versionCode="23"
|
||||
android:versionName="0.5.5-alpha" >
|
||||
android:versionCode="24"
|
||||
android:versionName="0.5.6-alpha" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="14"
|
||||
|
@ -46,13 +46,9 @@
|
||||
<Type type="custom" />
|
||||
</DataKind>
|
||||
|
||||
<DataKind
|
||||
kind="photo"
|
||||
maxOccurs="1" />
|
||||
<DataKind kind="photo" maxOccurs="1" />
|
||||
|
||||
<DataKind
|
||||
kind="organization"
|
||||
maxOccurs="1" />
|
||||
<DataKind kind="organization" maxOccurs="1" />
|
||||
|
||||
<DataKind kind="im" >
|
||||
<Type type="aim" />
|
||||
@ -66,17 +62,11 @@
|
||||
<Type type="custom" />
|
||||
</DataKind>
|
||||
|
||||
<DataKind
|
||||
kind="nickname"
|
||||
maxOccurs="1" />
|
||||
<DataKind kind="nickname" maxOccurs="1" />
|
||||
|
||||
<DataKind
|
||||
kind="note"
|
||||
maxOccurs="1" />
|
||||
<DataKind kind="note" maxOccurs="1" />
|
||||
|
||||
<DataKind
|
||||
kind="postal"
|
||||
needsStructured="true" >
|
||||
<DataKind kind="postal" needsStructured="true" >
|
||||
<Type type="home" />
|
||||
<Type type="work" />
|
||||
<Type type="other" />
|
||||
@ -85,13 +75,8 @@
|
||||
|
||||
<DataKind kind="website" />
|
||||
|
||||
<DataKind
|
||||
dateWithTime="false"
|
||||
kind="event" >
|
||||
<Type
|
||||
maxOccurs="1"
|
||||
type="birthday"
|
||||
yearOptional="false" />
|
||||
<DataKind kind="event" dateWithTime="false" >
|
||||
<Type maxOccurs="1" type="birthday" yearOptional="false" />
|
||||
<Type type="anniversary" />
|
||||
</DataKind>
|
||||
|
||||
|
@ -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",
|
||||
|
||||
|
@ -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);
|
||||
|
@ -193,7 +193,7 @@ public class LocalAddressBook extends LocalCollection<Contact> {
|
||||
}
|
||||
|
||||
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<Contact> {
|
||||
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<Contact> {
|
||||
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<Contact> {
|
||||
protected Builder buildPhoneNumber(Builder builder, Telephone number) {
|
||||
int typeCode = Phone.TYPE_OTHER;
|
||||
String typeLabel = null;
|
||||
boolean is_primary = false;
|
||||
|
||||
Set<TelephoneType> 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<Contact> {
|
||||
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<Contact> {
|
||||
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<Contact> {
|
||||
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;
|
||||
|
@ -95,7 +95,7 @@ public class LocalCalendar extends LocalCollection<Event> {
|
||||
|
||||
@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<Event> {
|
||||
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<Event> {
|
||||
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());
|
||||
|
||||
|
@ -67,8 +67,43 @@ public abstract class LocalCollection<T extends Resource> {
|
||||
|
||||
// content provider (= database) querying
|
||||
|
||||
public long[] findDirty() throws LocalStorageException {
|
||||
String where = entryColumnDirty() + "=1";
|
||||
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[] 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 {
|
||||
@ -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 {
|
||||
try {
|
||||
@Cleanup Cursor cursor = providerClient.query(ContentUris.withAppendedId(entriesURI(), localID),
|
||||
|
@ -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) {
|
||||
|
@ -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"));
|
||||
|
@ -206,7 +206,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
|
||||
dav.propfind(HttpPropfind.Mode.MEMBERS_COLLECTIONS);
|
||||
List<WebDavResource> 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());
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user