Version bump to 0.7.5

* account settings: show whether CardDAV server supports VCard 4.0
* CardDAV GET: ask for VCard 3.0 or VCard 4.0 (preferred) contacts
* CardDAV multiget: ask for VCard 4.0 contacts if the server supports it
* CardDAV PUT: send VCard 4.0 contacts if the server supports it
* import Apache httpclient-android rev. 1652769 correctly (hopefully fixes #491)
pull/2/head
Ricki Hirner 9 years ago
parent f738f74dea
commit b5c99265c3

@ -1,62 +0,0 @@
/*
* Copyright (c) 2013 2015 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.davdroid;
import java.io.IOException;
import java.io.InputStream;
import lombok.Cleanup;
import net.fortuna.ical4j.data.ParserException;
import android.content.res.AssetManager;
import android.test.InstrumentationTestCase;
import at.bitfire.davdroid.resource.Contact;
import ezvcard.property.Impp;
public class ContactTest extends InstrumentationTestCase {
AssetManager assetMgr;
public void setUp() {
assetMgr = getInstrumentation().getContext().getResources().getAssets();
}
public void testIMPP() throws IOException {
Contact c = parseVCard("impp.vcf");
assertEquals("test mctest", c.getDisplayName());
Impp jabber = c.getImpps().get(0);
assertNull(jabber.getProtocol());
assertEquals("test-without-valid-scheme@test.tld", jabber.getHandle());
}
public void testParseVcard3() throws IOException, ParserException {
Contact c = parseVCard("vcard3-sample1.vcf");
assertEquals("Forrest Gump", c.getDisplayName());
assertEquals("Forrest", c.getGivenName());
assertEquals("Gump", c.getFamilyName());
assertEquals(2, c.getPhoneNumbers().size());
assertEquals("(111) 555-1212", c.getPhoneNumbers().get(0).getText());
assertEquals(1, c.getEmails().size());
assertEquals("forrestgump@example.com", c.getEmails().get(0).getValue());
assertFalse(c.isStarred());
}
private Contact parseVCard(String fileName) throws IOException {
@Cleanup InputStream in = assetMgr.open(fileName, AssetManager.ACCESS_STREAMING);
Contact c = new Contact(fileName, null);
c.parseEntity(in, null);
return c;
}
}

@ -7,6 +7,11 @@
*/
package at.bitfire.davdroid.resource;
import android.content.res.AssetManager;
import android.test.InstrumentationTestCase;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
@ -15,16 +20,10 @@ import java.util.Arrays;
import at.bitfire.davdroid.webdav.DavException;
import at.bitfire.davdroid.webdav.HttpException;
import ezvcard.VCardVersion;
import ezvcard.property.Email;
import ezvcard.property.Telephone;
import lombok.Cleanup;
import android.content.res.AssetManager;
import android.test.InstrumentationTestCase;
import org.apache.commons.io.IOUtils;
import at.bitfire.davdroid.resource.Contact;
import at.bitfire.davdroid.resource.InvalidResourceException;
public class ContactTest extends InstrumentationTestCase {
AssetManager assetMgr;
@ -32,6 +31,19 @@ public class ContactTest extends InstrumentationTestCase {
public void setUp() throws IOException, InvalidResourceException {
assetMgr = getInstrumentation().getContext().getResources().getAssets();
}
public void testGenerateDifferentVersions() throws Exception {
Contact c = new Contact("test.vcf", null);
// should generate VCard 3.0 by default
assertEquals("text/vcard", c.getMimeType());
assertTrue(new String(c.toEntity().toByteArray()).contains("VERSION:3.0"));
// now let's generate VCard 4.0
c.setVCardVersion(VCardVersion.V4_0);
assertEquals("text/vcard;version=4.0", c.getMimeType());
assertTrue(new String(c.toEntity().toByteArray()).contains("VERSION:4.0"));
}
public void testReferenceVCard() throws IOException, InvalidResourceException {
Contact c = parseVCF("reference.vcf");

@ -7,16 +7,16 @@
*/
package at.bitfire.davdroid.resource;
import android.content.res.AssetManager;
import android.test.InstrumentationTestCase;
import android.text.format.Time;
import net.fortuna.ical4j.data.ParserException;
import java.io.IOException;
import java.io.InputStream;
import lombok.Cleanup;
import net.fortuna.ical4j.data.ParserException;
import android.content.res.AssetManager;
import android.test.InstrumentationTestCase;
import android.text.format.Time;
import at.bitfire.davdroid.resource.Event;
import at.bitfire.davdroid.resource.InvalidResourceException;
public class EventTest extends InstrumentationTestCase {
AssetManager assetMgr;

@ -7,19 +7,13 @@
*/
package at.bitfire.davdroid.resource;
import java.util.Calendar;
import lombok.Cleanup;
import android.Manifest;
import android.accounts.Account;
import android.annotation.TargetApi;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
@ -30,11 +24,11 @@ import android.provider.CalendarContract.Calendars;
import android.provider.CalendarContract.Events;
import android.provider.CalendarContract.Reminders;
import android.test.InstrumentationTestCase;
import android.test.IsolatedContext;
import android.test.mock.MockContentResolver;
import android.util.Log;
import at.bitfire.davdroid.resource.LocalCalendar;
import at.bitfire.davdroid.resource.LocalStorageException;
import java.util.Calendar;
import lombok.Cleanup;
public class LocalCalendarTest extends InstrumentationTestCase {

@ -9,7 +9,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="at.bitfire.davdroid"
android:versionCode="59" android:versionName="0.7.3"
android:versionCode="61" android:versionName="0.7.5"
android:installLocation="internalOnly">
<uses-sdk

@ -9,7 +9,7 @@ package at.bitfire.davdroid;
public class Constants {
public static final String
APP_VERSION = "0.7.3",
APP_VERSION = "0.7.5",
ACCOUNT_TYPE = "bitfire.at.davdroid",
WEB_URL_HELP = "https://davdroid.bitfire.at/configuration?pk_campaign=davdroid-app",
WEB_URL_VIEW_LOGS = "https://github.com/bitfireAT/davdroid/wiki/How-to-view-the-logs";

@ -7,17 +7,20 @@
*/
package at.bitfire.davdroid.resource;
import android.accounts.Account;
import org.apache.http.impl.client.CloseableHttpClient;
import java.net.URISyntaxException;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import at.bitfire.davdroid.webdav.DavMultiget;
public class CalDavCalendar extends RemoteCollection<Event> {
//private final static String TAG = "davdroid.CalDavCalendar";
@Override
protected String memberContentType() {
protected String memberAcceptedMimeTypes()
{
return "text/calendar";
}

@ -7,23 +7,28 @@
*/
package at.bitfire.davdroid.resource;
import android.accounts.Account;
import org.apache.http.impl.client.CloseableHttpClient;
import java.net.URISyntaxException;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import at.bitfire.davdroid.webdav.DavMultiget;
import ezvcard.VCardVersion;
public class CardDavAddressBook extends RemoteCollection<Contact> {
//private final static String TAG = "davdroid.CardDavAddressBook";
AccountSettings accountSettings;
@Override
protected String memberContentType() {
return Contact.MIME_TYPE;
protected String memberAcceptedMimeTypes() {
return "text/vcard;q=0.8, text/vcard;version=4.0";
}
@Override
protected DavMultiget.Type multiGetType() {
return DavMultiget.Type.ADDRESS_BOOK;
return accountSettings.getAddressBookVCardVersion() == VCardVersion.V4_0 ?
DavMultiget.Type.ADDRESS_BOOK_V4 : DavMultiget.Type.ADDRESS_BOOK;
}
@Override
@ -32,7 +37,8 @@ public class CardDavAddressBook extends RemoteCollection<Contact> {
}
public CardDavAddressBook(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
public CardDavAddressBook(AccountSettings settings, CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
super(httpClient, baseURL, user, password, preemptiveAuth);
accountSettings = settings;
}
}

@ -65,9 +65,9 @@ import lombok.ToString;
@ToString(callSuper = true)
public class Contact extends Resource {
private final static String TAG = "davdroid.Contact";
public final static String MIME_TYPE = "text/vcard";
@Getter @Setter protected VCardVersion vCardVersion = VCardVersion.V3_0;
public final static String
PROPERTY_STARRED = "X-DAVDROID-STARRED",
PROPERTY_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME",
@ -110,11 +110,11 @@ public class Contact extends Resource {
/* instance methods */
public Contact(String name, String ETag) {
Contact(String name, String ETag) {
super(name, ETag);
}
public Contact(long localID, String resourceName, String eTag) {
Contact(long localID, String resourceName, String eTag) {
super(localID, resourceName, eTag);
}
@ -300,6 +300,14 @@ public class Contact extends Resource {
}
}
@Override
public String getMimeType() {
if (vCardVersion == VCardVersion.V4_0)
return "text/vcard;version=4.0";
else
return "text/vcard";
}
@Override
public ByteArrayOutputStream toEntity() throws IOException {
@ -409,14 +417,14 @@ public class Contact extends Resource {
vcard.setRevision(Revision.now());
// validate and print warnings
ValidationWarnings warnings = vcard.validate(VCardVersion.V3_0);
ValidationWarnings warnings = vcard.validate(vCardVersion);
if (!warnings.isEmpty())
Log.w(TAG, "Created potentially invalid VCard! " + warnings);
ByteArrayOutputStream os = new ByteArrayOutputStream();
Ezvcard
.write(vcard)
.version(VCardVersion.V3_0)
.version(vCardVersion)
.versionStrict(false)
.prodId(false) // we provide our own PRODID
.go(os);

@ -75,8 +75,6 @@ import lombok.Setter;
public class Event extends Resource {
private final static String TAG = "davdroid.Event";
public final static String MIME_TYPE = "text/calendar";
private final static TimeZoneRegistry tzRegistry = new DefaultTimeZoneRegistryFactory().createRegistry();
@Getter @Setter protected RecurrenceId recurrenceId;
@ -236,6 +234,11 @@ public class Event extends Resource {
}
@Override
public String getMimeType() {
return "text/calendar";
}
@Override
@SuppressWarnings("unchecked")
public ByteArrayOutputStream toEntity() throws IOException {

@ -123,7 +123,9 @@ public class LocalAddressBook extends LocalCollection<Contact> {
/* create/update/delete */
public Contact newResource(long localID, String resourceName, String eTag) {
return new Contact(localID, resourceName, eTag);
Contact c = new Contact(localID, resourceName, eTag);
c.setVCardVersion(accountSettings.getAddressBookVCardVersion());
return c;
}
public void deleteAllExceptRemoteNames(Resource[] remoteResources) {

@ -7,12 +7,12 @@
*/
package at.bitfire.davdroid.resource;
import android.accounts.Account;
import android.util.Log;
import net.fortuna.ical4j.model.ValidationException;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.utils.URIUtilsHC4;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.ByteArrayInputStream;
@ -21,11 +21,11 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import at.bitfire.davdroid.URIUtils;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import at.bitfire.davdroid.webdav.DavException;
import at.bitfire.davdroid.webdav.DavMultiget;
import at.bitfire.davdroid.webdav.DavNoContentException;
@ -50,8 +50,7 @@ public abstract class RemoteCollection<T extends Resource> {
URI baseURI;
@Getter WebDavResource collection;
abstract protected String memberContentType();
abstract protected String memberAcceptedMimeTypes();
abstract protected DavMultiget.Type multiGetType();
abstract protected T newResourceSkeleton(String name, String ETag);
@ -132,14 +131,7 @@ public abstract class RemoteCollection<T extends Resource> {
public Resource get(Resource resource) throws URISyntaxException, IOException, HttpException, DavException, InvalidResourceException {
WebDavResource member = new WebDavResource(collection, resource.getName());
if (resource instanceof Contact)
member.get(Contact.MIME_TYPE);
else if (resource instanceof Event)
member.get(Event.MIME_TYPE);
else {
Log.wtf(TAG, "Should fetch something, but neither contact nor calendar");
throw new InvalidResourceException("Didn't now which MIME type to accept");
}
member.get(memberAcceptedMimeTypes());
byte[] data = member.getContent();
if (data == null)
@ -157,7 +149,7 @@ public abstract class RemoteCollection<T extends Resource> {
// returns ETag of the created resource, if returned by server
public String add(Resource res) throws URISyntaxException, IOException, HttpException, ValidationException {
WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag());
member.setContentType(memberContentType());
member.setContentType(res.getMimeType());
@Cleanup ByteArrayOutputStream os = res.toEntity();
String eTag = member.put(os.toByteArray(), PutMode.ADD_DONT_OVERWRITE);
@ -178,7 +170,7 @@ public abstract class RemoteCollection<T extends Resource> {
// returns ETag of the updated resource, if returned by server
public String update(Resource res) throws URISyntaxException, IOException, HttpException, ValidationException {
WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag());
member.setContentType(memberContentType());
member.setContentType(res.getMimeType());
@Cleanup ByteArrayOutputStream os = res.toEntity();
String eTag = member.put(os.toByteArray(), PutMode.UPDATE_DONT_OVERWRITE);

@ -30,7 +30,6 @@ public abstract class Resource {
@Getter @Setter protected String uid;
@Getter protected long localID;
public Resource(String name, String ETag) {
this.name = name;
this.ETag = ETag;
@ -50,6 +49,10 @@ public abstract class Resource {
**/
public abstract void parseEntity(InputStream entity, AssetDownloader downloader) throws IOException, InvalidResourceException;
/* returns the MIME type that toEntity() will produce */
public abstract String getMimeType();
/** writes the resource data to an output stream (for instance, .vcf file for Contact) */
public abstract ByteArrayOutputStream toEntity() throws IOException;

@ -67,7 +67,7 @@ public class ContactsSyncAdapterService extends Service {
try {
LocalCollection<?> database = new LocalAddressBook(account, provider, settings);
RemoteCollection<?> dav = new CardDavAddressBook(httpClient, addressBookURL, userName, password, preemptive);
RemoteCollection<?> dav = new CardDavAddressBook(settings, httpClient, addressBookURL, userName, password, preemptive);
Map<LocalCollection<?>, RemoteCollection<?>> map = new HashMap<LocalCollection<?>, RemoteCollection<?>>();
map.put(database, dav);

@ -9,15 +9,18 @@
package at.bitfire.davdroid.ui.settings;
import android.accounts.Account;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.SwitchPreference;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import ezvcard.VCardVersion;
import lombok.Setter;
public class AccountFragment extends PreferenceFragment {
@ -38,6 +41,7 @@ public class AccountFragment extends PreferenceFragment {
public void readFromAccount() {
final AccountSettings settings = new AccountSettings(getActivity(), account);
// category: authentication
final EditTextPreference prefUserName = (EditTextPreference)findPreference("username");
prefUserName.setSummary(settings.getUserName());
prefUserName.setText(settings.getUserName());
@ -61,7 +65,7 @@ public class AccountFragment extends PreferenceFragment {
}
});
final CheckBoxPreference prefPreemptive = (CheckBoxPreference)findPreference("preemptive");
final SwitchPreference prefPreemptive = (SwitchPreference)findPreference("preemptive");
prefPreemptive.setChecked(settings.getPreemptiveAuth());
prefPreemptive.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
@ -72,6 +76,7 @@ public class AccountFragment extends PreferenceFragment {
}
});
// category: synchronization
final ListPreference prefSyncContacts = (ListPreference)findPreference("sync_interval_contacts");
final Long syncIntervalContacts = settings.getContactsSyncInterval();
if (syncIntervalContacts != null) {
@ -113,5 +118,23 @@ public class AccountFragment extends PreferenceFragment {
prefSyncCalendars.setEnabled(false);
prefSyncCalendars.setSummary(R.string.settings_sync_summary_not_available);
}
// category: address book
final CheckBoxPreference prefVCard4 = (CheckBoxPreference) findPreference("vcard4_support");
if (settings.getAddressBookURL() != null) { // does this account even have an address book?
final VCardVersion vCardVersion = settings.getAddressBookVCardVersion();
prefVCard4.setChecked(vCardVersion == VCardVersion.V4_0);
prefVCard4.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
// don't change the value (it's not really a setting, only a display)
return false;
}
});
} else {
// account doesn't have an adress book, disable contact settings
prefVCard4.setEnabled(false);
}
}
}

@ -18,6 +18,7 @@ import java.util.List;
public class DavMultiget {
public enum Type {
ADDRESS_BOOK,
ADDRESS_BOOK_V4,
CALENDAR
}
@ -29,15 +30,24 @@ public class DavMultiget {
public static DavMultiget newRequest(Type type, String names[]) {
DavMultiget multiget = (type == Type.ADDRESS_BOOK) ? new DavAddressbookMultiget() : new DavCalendarMultiget();
DavMultiget multiget = (type == Type.CALENDAR) ? new DavCalendarMultiget() : new DavAddressbookMultiget();
multiget.prop = new DavProp();
multiget.prop.getetag = new DavProp.GetETag();
if (type == Type.ADDRESS_BOOK)
multiget.prop.addressData = new DavProp.AddressData();
else if (type == Type.CALENDAR)
multiget.prop.calendarData = new DavProp.CalendarData();
switch (type) {
case ADDRESS_BOOK:
multiget.prop.addressData = new DavProp.AddressData();
break;
case ADDRESS_BOOK_V4:
DavProp.AddressData addressData = new DavProp.AddressData();
addressData.setContentType("text/vcard");
addressData.setVersion("4.0");
multiget.prop.addressData = addressData;
break;
case CALENDAR:
multiget.prop.calendarData = new DavProp.CalendarData();
}
multiget.hrefs = new ArrayList<DavHref>(names.length);
for (String name : names)

@ -17,6 +17,7 @@ import org.simpleframework.xml.Text;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
@Namespace(prefix="D",reference="DAV:")
@Root(strict=false)
@ -199,6 +200,12 @@ public class DavProp {
@Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav")
public static class AddressData {
@Attribute(name="content-type", required=false)
@Getter @Setter String contentType;
@Attribute(required=false)
@Getter @Setter String version;
@Text(required=false)
@Getter String vcard;
}

@ -335,9 +335,9 @@ public class WebDavResource {
/* resource operations */
public void get(String acceptedType) throws URISyntaxException, IOException, HttpException, DavException {
public void get(String acceptedMimeTypes) throws URISyntaxException, IOException, HttpException, DavException {
HttpGetHC4 get = new HttpGetHC4(location);
get.addHeader("Accept", acceptedType);
get.addHeader("Accept", acceptedMimeTypes);
@Cleanup CloseableHttpResponse response = httpClient.execute(get, context);
checkResponse(response);

@ -137,6 +137,10 @@
<item>Alle 4 Stunden</item>
<item>Täglich</item>
</string-array>
<string name="settings_carddav">Adressbuch</string>
<string name="settings_carddav_vcard4_support">VCard 4.0-Unterstützung</string>
<string name="settings_carddav_vcard4_supported">Kontakte werden als VCard 4.0 gesendet</string>
<string name="settings_carddav_vcard4_not_supported">Kontakte werden als VCard 3.0 gesendet</string>
<string name="setup_select_collections">DAVdroid: Ordner auswählen</string>
<string name="setup_neither_caldav_nor_carddav">An dieser Adresse konnte kein CalDAV- oder CardDAV-Dienst gefunden werden.</string>

@ -151,6 +151,10 @@
<item>Every 4 hours</item>
<item>Once a day</item>
</string-array>
<string name="settings_carddav">Address book</string>
<string name="settings_carddav_vcard4_support">VCard 4.0 support</string>
<string name="settings_carddav_vcard4_supported">Contacts are sent in VCard 4.0 format</string>
<string name="settings_carddav_vcard4_not_supported">Contacts are sent in VCard 3.0 format</string>
<string name="setup_select_collections">DAVdroid: Select collections</string>
<string name="setup_neither_caldav_nor_carddav">No CalDAV-/CardDAV service is available at this location.</string>

@ -25,7 +25,7 @@
android:summary="@string/settings_password_summary"
android:dialogTitle="@string/settings_enter_password" />
<CheckBoxPreference
<SwitchPreference
android:key="preemptive"
android:persistent="false"
android:title="@string/settings_preemptive"
@ -52,4 +52,15 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_carddav">
<CheckBoxPreference
android:key="vcard4_support"
android:title="@string/settings_carddav_vcard4_support"
android:persistent="false"
android:summaryOn="@string/settings_carddav_vcard4_supported"
android:summaryOff="@string/settings_carddav_vcard4_not_supported" />
</PreferenceCategory>
</PreferenceScreen>

@ -282,7 +282,7 @@ public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactor
}
}
@TargetApi(17)
@TargetApi(9)
public Socket createLayeredSocket(
final Socket socket,
final String target,
@ -319,7 +319,7 @@ public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactor
prepareSocket(sslsock);
// Android specific code to enable SNI
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Enabling SNI for " + target);
}

Loading…
Cancel
Save