diff --git a/app/build.gradle b/app/build.gradle index f57f6341..6b2cd06a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,8 +15,9 @@ android { buildTypes { release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + minifyEnabled false + // minifyEnabled true + // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } } dexOptions { diff --git a/app/lint.xml b/app/lint.xml index d191f74b..69044e98 100644 --- a/app/lint.xml +++ b/app/lint.xml @@ -1,5 +1,6 @@ + diff --git a/build.xml b/build.xml deleted file mode 100644 index 4a6bb871..00000000 --- a/build.xml +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Creating library output jar file... - - - - - - - Custom jar packaging exclusion: ${android.package.excludes} - - - - - - - - - - - - - - - - - - diff --git a/compile-libs/lombok.jar b/compile-libs/lombok.jar deleted file mode 100644 index 4141433c..00000000 Binary files a/compile-libs/lombok.jar and /dev/null differ diff --git a/eclipse-libs/lombok-api.jar b/eclipse-libs/lombok-api.jar deleted file mode 100644 index db3dff79..00000000 Binary files a/eclipse-libs/lombok-api.jar and /dev/null differ diff --git a/libs/backport-util-concurrent-3.1.jar b/libs/backport-util-concurrent-3.1.jar deleted file mode 100644 index 3a4c2797..00000000 Binary files a/libs/backport-util-concurrent-3.1.jar and /dev/null differ diff --git a/libs/commons-codec-1.8.jar b/libs/commons-codec-1.8.jar deleted file mode 100644 index 32f84c92..00000000 Binary files a/libs/commons-codec-1.8.jar and /dev/null differ diff --git a/libs/commons-io-2.4.jar b/libs/commons-io-2.4.jar deleted file mode 100644 index 90035a4f..00000000 Binary files a/libs/commons-io-2.4.jar and /dev/null differ diff --git a/libs/commons-lang-2.6.jar b/libs/commons-lang-2.6.jar deleted file mode 100644 index 98467d3a..00000000 Binary files a/libs/commons-lang-2.6.jar and /dev/null differ diff --git a/libs/commons-logging-1.1.3.jar b/libs/commons-logging-1.1.3.jar deleted file mode 100644 index ab512540..00000000 Binary files a/libs/commons-logging-1.1.3.jar and /dev/null differ diff --git a/libs/ez-vcard-0.9.6.jar b/libs/ez-vcard-0.9.6.jar deleted file mode 100644 index 0bb07c9d..00000000 Binary files a/libs/ez-vcard-0.9.6.jar and /dev/null differ diff --git a/libs/httpclientandroidlib-1.2.1.jar b/libs/httpclientandroidlib-1.2.1.jar deleted file mode 100644 index f409b0b2..00000000 Binary files a/libs/httpclientandroidlib-1.2.1.jar and /dev/null differ diff --git a/libs/ical4j-1.0.6-davdroid141027.jar b/libs/ical4j-1.0.6-davdroid141027.jar deleted file mode 100644 index 6a6ec1bf..00000000 Binary files a/libs/ical4j-1.0.6-davdroid141027.jar and /dev/null differ diff --git a/libs/org.xbill.dns_2.1.6.jar b/libs/org.xbill.dns_2.1.6.jar deleted file mode 100644 index 3d42fbc4..00000000 Binary files a/libs/org.xbill.dns_2.1.6.jar and /dev/null differ diff --git a/libs/simple-xml-2.7.jar b/libs/simple-xml-2.7.jar deleted file mode 100644 index cb12687a..00000000 Binary files a/libs/simple-xml-2.7.jar and /dev/null differ diff --git a/proguard-project.txt b/proguard-project.txt deleted file mode 100644 index 7c96635c..00000000 --- a/proguard-project.txt +++ /dev/null @@ -1,28 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: --dontusemixedcaseclassnames --dontskipnonpubliclibraryclassmembers - --dontwarn edu.emory.mathcs.backport.** --dontwarn ezvcard.** --dontwarn net.fortuna.** --dontwarn org.apache.** --dontwarn org.simpleframework.xml.** - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/project.properties b/project.properties deleted file mode 100644 index 6e18427a..00000000 --- a/project.properties +++ /dev/null @@ -1,14 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-21 diff --git a/src/at/bitfire/davdroid/ArrayUtils.java b/src/at/bitfire/davdroid/ArrayUtils.java deleted file mode 100644 index f4f050db..00000000 --- a/src/at/bitfire/davdroid/ArrayUtils.java +++ /dev/null @@ -1,33 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.lang.reflect.Array; - -public class ArrayUtils { - - @SuppressWarnings("unchecked") - public static T[][] partition(T[] bigArray, int max) { - int nItems = bigArray.length; - int nPartArrays = (nItems + max-1)/max; - - T[][] partArrays = (T[][])Array.newInstance(bigArray.getClass().getComponentType(), nPartArrays, 0); - - // nItems is now the number of remaining items - for (int i = 0; nItems > 0; i++) { - int n = (nItems < max) ? nItems : max; - partArrays[i] = (T[])Array.newInstance(bigArray.getClass().getComponentType(), n); - System.arraycopy(bigArray, i*max, partArrays[i], 0, n); - - nItems -= n; - } - - return partArrays; - } - -} diff --git a/src/at/bitfire/davdroid/Constants.java b/src/at/bitfire/davdroid/Constants.java deleted file mode 100644 index cd0a4907..00000000 --- a/src/at/bitfire/davdroid/Constants.java +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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; - -public class Constants { - public static final String - APP_VERSION = "0.6.8", - ACCOUNT_TYPE = "bitfire.at.davdroid", - WEB_URL_HELP = "https://davdroid.bitfire.at/configuration?pk_campaign=davdroid-app", - - SETTING_DISABLE_COMPRESSION = "disable_compression", - SETTING_NETWORK_LOGGING = "network_logging"; -} diff --git a/src/at/bitfire/davdroid/MainActivity.java b/src/at/bitfire/davdroid/MainActivity.java deleted file mode 100644 index a665f6a0..00000000 --- a/src/at/bitfire/davdroid/MainActivity.java +++ /dev/null @@ -1,82 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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 android.app.Activity; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.provider.Settings; -import android.text.Html; -import android.text.method.LinkMovementMethod; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; -import at.bitfire.davdroid.syncadapter.AddAccountActivity; -import at.bitfire.davdroid.syncadapter.GeneralSettingsActivity; - -public class MainActivity extends Activity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_main); - - TextView tvWorkaround = (TextView)findViewById(R.id.text_workaround); - if (fromPlayStore()) { - tvWorkaround.setVisibility(View.VISIBLE); - tvWorkaround.setText(Html.fromHtml(getString(R.string.html_main_workaround))); - tvWorkaround.setMovementMethod(LinkMovementMethod.getInstance()); - } - - TextView tvInfo = (TextView)findViewById(R.id.text_info); - tvInfo.setText(Html.fromHtml(getString(R.string.html_main_info, Constants.APP_VERSION))); - tvInfo.setMovementMethod(LinkMovementMethod.getInstance()); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.main_activity, menu); - return true; - } - - - public void addAccount(MenuItem item) { - Intent intent = new Intent(this, AddAccountActivity.class); - startActivity(intent); - } - - public void showDebugSettings(MenuItem item) { - Intent intent = new Intent(this, GeneralSettingsActivity.class); - startActivity(intent); - } - - public void showSyncSettings(MenuItem item) { - Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS); - startActivity(intent); - } - - public void showWebsite(MenuItem item) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(Constants.WEB_URL_HELP + "&pk_kwd=main-activity")); - startActivity(intent); - } - - - private boolean fromPlayStore() { - try { - return "com.android.vending".equals(getPackageManager().getInstallerPackageName("at.bitfire.davdroid")); - } catch(IllegalArgumentException e) { - } - return false; - } -} diff --git a/src/at/bitfire/davdroid/URLUtils.java b/src/at/bitfire/davdroid/URLUtils.java deleted file mode 100644 index 8c6860d0..00000000 --- a/src/at/bitfire/davdroid/URLUtils.java +++ /dev/null @@ -1,98 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.net.MalformedURLException; -import java.net.URL; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import android.annotation.SuppressLint; -import android.util.Log; - -@SuppressLint("DefaultLocale") -public class URLUtils { - private static final String TAG = "davdroid.URIUtils"; - - - public static String ensureTrailingSlash(String href) { - if (!href.endsWith("/")) { - Log.d(TAG, "Implicitly appending trailing slash to collection " + href); - return href + "/"; - } else - return href; - } - - public static URL ensureTrailingSlash(URL href) { - if (!href.getPath().endsWith("/")) - try { - URL newURL = new URL(href, href.getPath() + "/"); - - // "@" is the only character that is not encoded - //newURL = new URI(newURI.toString().replaceAll("@", "%40")); - - Log.d(TAG, "Implicitly appending trailing slash to collection " + href + " -> " + newURL); - return newURL; - } catch (MalformedURLException e) { - Log.e(TAG, "Couldn't append trailing slash to collection URI", e); - } - return href; - } - - - /** 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 - // $3: hostname (may end with ":port") or empty - // $4: path or empty - - Matcher m = p.matcher(original); - if (m.matches()) { - String schema = m.group(2), - host = m.group(3), - path = m.group(4); - - if (host != null) - // sanitize host name (don't replace "[", "]", ":" for IP address literals and port numbers) - // "@" should be used for user name/password, but this case shouldn't appear in our URLs - for (char c : new char[] { '@', ' ', '<', '>', '"', '#', '{', '}', '|', '\\', '^', '~', '`' }) - host = host.replace(String.valueOf(c), "%" + Integer.toHexString(c)); - - if (path != null) - // rewrite reserved characters: - // ":" and "@" may be used in the host part but not in the path - // rewrite unsafe characters: - // " ", "<", ">", """, "#", "{", "}", "|", "\", "^", "~", "["], "]", "`" - // do not rewrite "%" because we assume that URLs should be already encoded correctly - for (char c : new char[] { ':', '@', ' ', '<', '>', '"', '#', '{', '}', '|', '\\', '^', '~', '[', ']', '`' }) - path = path.replace(String.valueOf(c), "%" + Integer.toHexString(c)); - - String url = (schema != null) ? schema : ""; - if (host != null) - url = url + "//" + host; - if (path != null) - url = url + path; - - if (!url.equals(original)) - Log.w(TAG, "Trying to repair invalid URL: " + original + " -> " + url); - return url; - - } else { - - Log.w(TAG, "Couldn't sanitize URL: " + original); - return original; - - } - } - -} diff --git a/src/at/bitfire/davdroid/resource/CalDavCalendar.java b/src/at/bitfire/davdroid/resource/CalDavCalendar.java deleted file mode 100644 index 770f479e..00000000 --- a/src/at/bitfire/davdroid/resource/CalDavCalendar.java +++ /dev/null @@ -1,37 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -import java.net.MalformedURLException; - -import at.bitfire.davdroid.webdav.DavMultiget; -import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient; - -public class CalDavCalendar extends RemoteCollection { - //private final static String TAG = "davdroid.CalDavCalendar"; - - @Override - protected String memberContentType() { - return "text/calendar"; - } - - @Override - protected DavMultiget.Type multiGetType() { - return DavMultiget.Type.CALENDAR; - } - - @Override - protected Event newResourceSkeleton(String name, String ETag) { - return new Event(name, ETag); - } - - - public CalDavCalendar(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws MalformedURLException { - super(httpClient, baseURL, user, password, preemptiveAuth); - } -} diff --git a/src/at/bitfire/davdroid/resource/CardDavAddressBook.java b/src/at/bitfire/davdroid/resource/CardDavAddressBook.java deleted file mode 100644 index eda04fb1..00000000 --- a/src/at/bitfire/davdroid/resource/CardDavAddressBook.java +++ /dev/null @@ -1,37 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -import java.net.MalformedURLException; - -import at.bitfire.davdroid.webdav.DavMultiget; -import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient; - -public class CardDavAddressBook extends RemoteCollection { - //private final static String TAG = "davdroid.CardDavAddressBook"; - - @Override - protected String memberContentType() { - return Contact.MIME_TYPE; - } - - @Override - protected DavMultiget.Type multiGetType() { - return DavMultiget.Type.ADDRESS_BOOK; - } - - @Override - protected Contact newResourceSkeleton(String name, String ETag) { - return new Contact(name, ETag); - } - - - public CardDavAddressBook(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws MalformedURLException { - super(httpClient, baseURL, user, password, preemptiveAuth); - } -} diff --git a/src/at/bitfire/davdroid/resource/Contact.java b/src/at/bitfire/davdroid/resource/Contact.java deleted file mode 100644 index d301450c..00000000 --- a/src/at/bitfire/davdroid/resource/Contact.java +++ /dev/null @@ -1,410 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; - -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import org.apache.commons.lang.StringUtils; - -import android.util.Log; -import at.bitfire.davdroid.Constants; -import ezvcard.Ezvcard; -import ezvcard.VCard; -import ezvcard.VCardVersion; -import ezvcard.ValidationWarnings; -import ezvcard.parameter.EmailType; -import ezvcard.parameter.ImageType; -import ezvcard.parameter.TelephoneType; -import ezvcard.property.Address; -import ezvcard.property.Anniversary; -import ezvcard.property.Birthday; -import ezvcard.property.Categories; -import ezvcard.property.Email; -import ezvcard.property.FormattedName; -import ezvcard.property.Impp; -import ezvcard.property.Logo; -import ezvcard.property.Nickname; -import ezvcard.property.Note; -import ezvcard.property.Organization; -import ezvcard.property.Photo; -import ezvcard.property.RawProperty; -import ezvcard.property.Revision; -import ezvcard.property.Role; -import ezvcard.property.Sound; -import ezvcard.property.Source; -import ezvcard.property.StructuredName; -import ezvcard.property.Telephone; -import ezvcard.property.Title; -import ezvcard.property.Uid; -import ezvcard.property.Url; - - -/** - * Represents a contact. Locally, this is a Contact in the Android - * device; remote, this is a VCard. - */ -@ToString(callSuper = true) -public class Contact extends Resource { - private final static String TAG = "davdroid.Contact"; - - public final static String MIME_TYPE = "text/vcard"; - - public final static String - PROPERTY_STARRED = "X-DAVDROID-STARRED", - PROPERTY_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME", - PROPERTY_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME", - PROPERTY_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME", - PROPERTY_SIP = "X-SIP"; - - public final static EmailType EMAIL_TYPE_MOBILE = EmailType.get("X-MOBILE"); - - public final static TelephoneType - PHONE_TYPE_CALLBACK = TelephoneType.get("X-CALLBACK"), - PHONE_TYPE_COMPANY_MAIN = TelephoneType.get("X-COMPANY_MAIN"), - PHONE_TYPE_RADIO = TelephoneType.get("X-RADIO"), - PHONE_TYPE_ASSISTANT = TelephoneType.get("X-ASSISTANT"), - PHONE_TYPE_MMS = TelephoneType.get("X-MMS"); - - @Getter @Setter private String unknownProperties; - - @Getter @Setter private boolean starred; - - @Getter @Setter private String displayName, nickName; - @Getter @Setter private String prefix, givenName, middleName, familyName, suffix; - @Getter @Setter private String phoneticGivenName, phoneticMiddleName, phoneticFamilyName; - @Getter @Setter private String note; - @Getter @Setter private Organization organization; - @Getter @Setter private String jobTitle, jobDescription; - - @Getter @Setter private byte[] photo; - - @Getter @Setter private Anniversary anniversary; - @Getter @Setter private Birthday birthDay; - - @Getter private List phoneNumbers = new LinkedList(); - @Getter private List emails = new LinkedList(); - @Getter private List impps = new LinkedList(); - @Getter private List
addresses = new LinkedList
(); - @Getter private List categories = new LinkedList(); - @Getter private List URLs = new LinkedList(); - - - /* instance methods */ - - public Contact(String name, String ETag) { - super(name, ETag); - } - - public Contact(long localID, String resourceName, String eTag) { - super(localID, resourceName, eTag); - } - - - @Override - public void initialize() { - generateUID(); - name = uid + ".vcf"; - } - - protected void generateUID() { - uid = UUID.randomUUID().toString(); - } - - - /* VCard methods */ - - @Override - public void parseEntity(InputStream is) throws IOException { - VCard vcard = Ezvcard.parse(is).first(); - if (vcard == null) - return; - - // now work through all supported properties - // supported properties are removed from the VCard after parsing - // so that only unknown properties are left and can be stored separately - - // UID - Uid uid = vcard.getUid(); - if (uid != null) { - this.uid = uid.getValue(); - vcard.removeProperties(Uid.class); - } else { - Log.w(TAG, "Received VCard without UID, generating new one"); - generateUID(); - } - - // X-DAVDROID-STARRED - RawProperty starred = vcard.getExtendedProperty(PROPERTY_STARRED); - if (starred != null && starred.getValue() != null) { - this.starred = starred.getValue().equals("1"); - vcard.removeExtendedProperty(PROPERTY_STARRED); - } else - this.starred = false; - - // FN - FormattedName fn = vcard.getFormattedName(); - if (fn != null) { - displayName = fn.getValue(); - vcard.removeProperties(FormattedName.class); - } else - Log.w(TAG, "Received invalid VCard without FN (formatted name) property"); - - // N - StructuredName n = vcard.getStructuredName(); - if (n != null) { - prefix = StringUtils.join(n.getPrefixes(), " "); - givenName = n.getGiven(); - middleName = StringUtils.join(n.getAdditional(), " "); - familyName = n.getFamily(); - suffix = StringUtils.join(n.getSuffixes(), " "); - vcard.removeProperties(StructuredName.class); - } - - // phonetic names - RawProperty - phoneticFirstName = vcard.getExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME), - phoneticMiddleName = vcard.getExtendedProperty(PROPERTY_PHONETIC_MIDDLE_NAME), - phoneticLastName = vcard.getExtendedProperty(PROPERTY_PHONETIC_LAST_NAME); - if (phoneticFirstName != null) { - phoneticGivenName = phoneticFirstName.getValue(); - vcard.removeExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME); - } - if (phoneticMiddleName != null) { - this.phoneticMiddleName = phoneticMiddleName.getValue(); - vcard.removeExtendedProperty(PROPERTY_PHONETIC_MIDDLE_NAME); - } - if (phoneticLastName != null) { - phoneticFamilyName = phoneticLastName.getValue(); - vcard.removeExtendedProperty(PROPERTY_PHONETIC_LAST_NAME); - } - - // TEL - phoneNumbers = vcard.getTelephoneNumbers(); - vcard.removeProperties(Telephone.class); - - // EMAIL - emails = vcard.getEmails(); - vcard.removeProperties(Email.class); - - // PHOTO - for (Photo photo : vcard.getPhotos()) { - this.photo = photo.getData(); - vcard.removeProperties(Photo.class); - break; - } - - // ORG - organization = vcard.getOrganization(); - vcard.removeProperties(Organization.class); - // TITLE - for (Title title : vcard.getTitles()) { - jobTitle = title.getValue(); - vcard.removeProperties(Title.class); - break; - } - // ROLE - for (Role role : vcard.getRoles()) { - this.jobDescription = role.getValue(); - vcard.removeProperties(Role.class); - break; - } - - // IMPP - impps = vcard.getImpps(); - vcard.removeProperties(Impp.class); - - // NICKNAME - Nickname nicknames = vcard.getNickname(); - if (nicknames != null) { - if (nicknames.getValues() != null) - nickName = StringUtils.join(nicknames.getValues(), ", "); - vcard.removeProperties(Nickname.class); - } - - // NOTE - List notes = new LinkedList(); - for (Note note : vcard.getNotes()) - notes.add(note.getValue()); - if (!notes.isEmpty()) - note = StringUtils.join(notes, "\n---\n"); - vcard.removeProperties(Note.class); - - // ADR - addresses = vcard.getAddresses(); - vcard.removeProperties(Address.class); - - // CATEGORY - Categories categories = vcard.getCategories(); - if (categories != null) - this.categories = categories.getValues(); - vcard.removeProperties(Categories.class); - - // URL - for (Url url : vcard.getUrls()) - URLs.add(url.getValue()); - vcard.removeProperties(Url.class); - - // BDAY - birthDay = vcard.getBirthday(); - vcard.removeProperties(Birthday.class); - // ANNIVERSARY - anniversary = vcard.getAnniversary(); - vcard.removeProperties(Anniversary.class); - - // X-SIP - for (RawProperty sip : vcard.getExtendedProperties(PROPERTY_SIP)) - impps.add(new Impp("sip", sip.getValue())); - vcard.removeExtendedProperty(PROPERTY_SIP); - - // remove binary properties because of potential OutOfMemory / TransactionTooLarge exceptions - vcard.removeProperties(Logo.class); - vcard.removeProperties(Sound.class); - // remove properties that don't apply anymore - vcard.removeProperties(Revision.class); - vcard.removeProperties(Source.class); - // store all remaining properties into unknownProperties - if (!vcard.getProperties().isEmpty() || !vcard.getExtendedProperties().isEmpty()) - try { - unknownProperties = vcard.write(); - } catch(Exception e) { - Log.w(TAG, "Couldn't store unknown properties (maybe illegal syntax), dropping them"); - } - } - - - @Override - public ByteArrayOutputStream toEntity() throws IOException { - VCard vcard = null; - try { - if (unknownProperties != null) - vcard = Ezvcard.parse(unknownProperties).first(); - } catch (Exception e) { - Log.w(TAG, "Couldn't parse original property set, beginning from scratch"); - } - if (vcard == null) - vcard = new VCard(); - - if (uid != null) - vcard.setUid(new Uid(uid)); - else - Log.wtf(TAG, "Generating VCard without UID"); - - if (starred) - vcard.setExtendedProperty(PROPERTY_STARRED, "1"); - - if (displayName != null) - vcard.setFormattedName(displayName); - else if (organization != null && organization.getValues() != null && organization.getValues().get(0) != null) - vcard.setFormattedName(organization.getValues().get(0)); - else - Log.w(TAG, "No FN (formatted name) available to generate VCard"); - - // N - if (familyName != null || middleName != null || givenName != null) { - StructuredName n = new StructuredName(); - if (prefix != null) - for (String p : StringUtils.split(prefix)) - n.addPrefix(p); - n.setGiven(givenName); - if (middleName != null) - for (String middle : StringUtils.split(middleName)) - n.addAdditional(middle); - n.setFamily(familyName); - if (suffix != null) - for (String s : StringUtils.split(suffix)) - n.addSuffix(s); - vcard.setStructuredName(n); - } - - // phonetic names - if (phoneticGivenName != null) - vcard.addExtendedProperty(PROPERTY_PHONETIC_FIRST_NAME, phoneticGivenName); - if (phoneticMiddleName != null) - vcard.addExtendedProperty(PROPERTY_PHONETIC_MIDDLE_NAME, phoneticMiddleName); - if (phoneticFamilyName != null) - vcard.addExtendedProperty(PROPERTY_PHONETIC_LAST_NAME, phoneticFamilyName); - - // TEL - for (Telephone phoneNumber : phoneNumbers) - vcard.addTelephoneNumber(phoneNumber); - - // EMAIL - for (Email email : emails) - vcard.addEmail(email); - - // ORG, TITLE, ROLE - if (organization != null) - vcard.setOrganization(organization); - if (jobTitle != null) - vcard.addTitle(jobTitle); - if (jobDescription != null) - vcard.addRole(jobDescription); - - // IMPP - for (Impp impp : impps) - vcard.addImpp(impp); - - // NICKNAME - if (!StringUtils.isBlank(nickName)) - vcard.setNickname(nickName); - - // NOTE - if (!StringUtils.isBlank(note)) - vcard.addNote(note); - - // ADR - for (Address address : addresses) - vcard.addAddress(address); - - // CATEGORY - if (!categories.isEmpty()) - vcard.setCategories(categories.toArray(new String[0])); - - // URL - for (String url : URLs) - vcard.addUrl(url); - - // ANNIVERSARY - if (anniversary != null) - vcard.setAnniversary(anniversary); - // BDAY - if (birthDay != null) - vcard.setBirthday(birthDay); - - // PHOTO - if (photo != null) - vcard.addPhoto(new Photo(photo, ImageType.JPEG)); - - // PRODID, REV - vcard.setProductId("DAVdroid/" + Constants.APP_VERSION + " (ez-vcard/" + Ezvcard.VERSION + ")"); - vcard.setRevision(Revision.now()); - - // validate and print warnings - ValidationWarnings warnings = vcard.validate(VCardVersion.V3_0); - if (!warnings.isEmpty()) - Log.w(TAG, "Created potentially invalid VCard! " + warnings); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - Ezvcard - .write(vcard) - .version(VCardVersion.V3_0) - .versionStrict(false) - .prodId(false) // we provide our own PRODID - .go(os); - return os; - } -} diff --git a/src/at/bitfire/davdroid/resource/DavResourceFinder.java b/src/at/bitfire/davdroid/resource/DavResourceFinder.java deleted file mode 100644 index 888a1e2f..00000000 --- a/src/at/bitfire/davdroid/resource/DavResourceFinder.java +++ /dev/null @@ -1,299 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -import java.io.Closeable; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.LinkedList; -import java.util.List; - -import org.xbill.DNS.Lookup; -import org.xbill.DNS.Record; -import org.xbill.DNS.SRVRecord; -import org.xbill.DNS.TXTRecord; -import org.xbill.DNS.TextParseException; -import org.xbill.DNS.Type; - -import android.content.Context; -import android.util.Log; -import at.bitfire.davdroid.R; -import at.bitfire.davdroid.webdav.DavException; -import at.bitfire.davdroid.webdav.DavHttpClient; -import at.bitfire.davdroid.webdav.DavIncapableException; -import at.bitfire.davdroid.webdav.HttpPropfind.Mode; -import at.bitfire.davdroid.webdav.NotAuthorizedException; -import at.bitfire.davdroid.webdav.WebDavResource; -import ch.boye.httpclientandroidlib.HttpException; -import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient; -import ezvcard.VCardVersion; - -public class DavResourceFinder implements Closeable { - private final static String TAG = "davdroid.DavResourceFinder"; - - protected Context context; - protected CloseableHttpClient httpClient; - - - public DavResourceFinder(Context context) { - this.context = context; - - // disable compression and enable network logging for debugging purposes - httpClient = DavHttpClient.create(true, true); - } - - @Override - public void close() throws IOException { - httpClient.close(); - } - - - public void findResources(ServerInfo serverInfo) throws URISyntaxException, DavException, HttpException, IOException { - // CardDAV - WebDavResource principal = getCurrentUserPrincipal(serverInfo, "carddav"); - if (principal != null) { - serverInfo.setCardDAV(true); - - principal.propfind(Mode.HOME_SETS); - String pathAddressBooks = principal.getAddressbookHomeSet(); - if (pathAddressBooks != null) { - Log.i(TAG, "Found address book home set: " + pathAddressBooks); - - WebDavResource homeSetAddressBooks = new WebDavResource(principal, pathAddressBooks); - if (checkHomesetCapabilities(homeSetAddressBooks, "addressbook")) { - homeSetAddressBooks.propfind(Mode.CARDDAV_COLLECTIONS); - - List addressBooks = new LinkedList(); - if (homeSetAddressBooks.getMembers() != null) - for (WebDavResource resource : homeSetAddressBooks.getMembers()) - if (resource.isAddressBook()) { - Log.i(TAG, "Found address book: " + resource.getLocation().getPath()); - ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo( - ServerInfo.ResourceInfo.Type.ADDRESS_BOOK, - resource.isReadOnly(), - resource.getLocation().toString(), - resource.getDisplayName(), - resource.getDescription(), resource.getColor() - ); - - VCardVersion version = resource.getVCardVersion(); - if (version == null) - version = VCardVersion.V3_0; // VCard 3.0 MUST be supported - info.setVCardVersion(version); - - addressBooks.add(info); - } - serverInfo.setAddressBooks(addressBooks); - } else - Log.w(TAG, "Found address-book home set, but it doesn't advertise CardDAV support"); - } - } - - // CalDAV - principal = getCurrentUserPrincipal(serverInfo, "caldav"); - if (principal != null) { - serverInfo.setCalDAV(true); - - principal.propfind(Mode.HOME_SETS); - String pathCalendars = principal.getCalendarHomeSet(); - if (pathCalendars != null) { - Log.i(TAG, "Found calendar home set: " + pathCalendars); - - WebDavResource homeSetCalendars = new WebDavResource(principal, pathCalendars); - if (checkHomesetCapabilities(homeSetCalendars, "calendar-access")) { - homeSetCalendars.propfind(Mode.CALDAV_COLLECTIONS); - - List calendars = new LinkedList(); - if (homeSetCalendars.getMembers() != null) - for (WebDavResource resource : homeSetCalendars.getMembers()) - if (resource.isCalendar()) { - Log.i(TAG, "Found calendar: " + resource.getLocation().getPath()); - if (resource.getSupportedComponents() != null) { - // CALDAV:supported-calendar-component-set available - boolean supportsEvents = false; - for (String supportedComponent : resource.getSupportedComponents()) - if (supportedComponent.equalsIgnoreCase("VEVENT")) - supportsEvents = true; - if (!supportsEvents) { // ignore collections without VEVENT support - Log.i(TAG, "Ignoring this calendar because of missing VEVENT support"); - continue; - } - } - ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo( - ServerInfo.ResourceInfo.Type.CALENDAR, - resource.isReadOnly(), - resource.getLocation().toString(), - resource.getDisplayName(), - resource.getDescription(), resource.getColor() - ); - info.setTimezone(resource.getTimezone()); - calendars.add(info); - } - serverInfo.setCalendars(calendars); - } else - Log.w(TAG, "Found calendar home set, but it doesn't advertise CalDAV support"); - } - } - - if (!serverInfo.isCalDAV() && !serverInfo.isCardDAV()) - throw new DavIncapableException(context.getString(R.string.setup_neither_caldav_nor_carddav)); - - } - - - /** - * Finds the initial service URL from a given base URI (HTTP[S] or mailto URI, user name, password) - * @param serverInfo User-given service information (including base URI, i.e. HTTP[S] URL+user name+password or mailto URI and password) - * @param serviceName Service name ("carddav" or "caldav") - * @return Initial service URL (HTTP/HTTPS), without user credentials - * @throws URISyntaxException when the user-given URI is invalid - * @throws MalformedURLException when the user-given URI is invalid - */ - public URL getInitialContextURL(ServerInfo serverInfo, String serviceName) throws URISyntaxException, MalformedURLException { - String scheme = null, - domain = null; - int port = -1; - String path = "/"; - - URI baseURI = serverInfo.getBaseURI(); - if ("mailto".equalsIgnoreCase(baseURI.getScheme())) { - // mailto URIs - String mailbox = serverInfo.getBaseURI().getSchemeSpecificPart(); - - // determine service FQDN - int pos = mailbox.lastIndexOf("@"); - if (pos == -1) - throw new URISyntaxException(mailbox, "Missing @ sign"); - - scheme = "https"; - domain = mailbox.substring(pos + 1); - if (domain.isEmpty()) - throw new URISyntaxException(mailbox, "Missing domain name"); - } else { - // HTTP(S) URLs - scheme = baseURI.getScheme(); - domain = baseURI.getHost(); - port = baseURI.getPort(); - path = baseURI.getPath(); - } - - // try to determine FQDN and port number using SRV records - try { - String name = "_" + serviceName + "s._tcp." + domain; - Log.d(TAG, "Looking up SRV records for " + name); - Record[] records = new Lookup(name, Type.SRV).run(); - if (records != null && records.length >= 1) { - SRVRecord srv = selectSRVRecord(records); - - scheme = "https"; - domain = srv.getTarget().toString(true); - port = srv.getPort(); - Log.d(TAG, "Found " + serviceName + "s service for " + domain + " -> " + domain + ":" + port); - - // SRV record found, look for TXT record too (for initial context path) - records = new Lookup(name, Type.TXT).run(); - if (records != null && records.length >= 1) { - TXTRecord txt = (TXTRecord)records[0]; - for (Object o : txt.getStrings().toArray()) { - String segment = (String)o; - if (segment.startsWith("path=")) { - path = segment.substring(5); - Log.d(TAG, "Found initial context path for " + serviceName + " at " + domain + " -> " + path); - break; - } - } - } - } - } catch (TextParseException e) { - throw new URISyntaxException(domain, "Invalid domain name"); - } - - if (port != -1) - return new URL(scheme, domain, port, path); - else - return new URL(scheme, domain, path); - } - - - /** - * Detects the current-user-principal for a given WebDavResource. At first, /.well-known/ is tried. Only - * if no current-user-principal can be detected for the .well-known location, the given location of the resource - * is tried. - * @param resource Location that will be queried - * @param serviceName Well-known service name ("carddav", "caldav") - * @return WebDavResource of current-user-principal for the given service, or null if it can't be found - * - * TODO: If a TXT record is given, always use it instead of trying .well-known first - */ - WebDavResource getCurrentUserPrincipal(ServerInfo serverInfo, String serviceName) throws URISyntaxException, IOException, NotAuthorizedException { - URL initialURL = getInitialContextURL(serverInfo, serviceName); - if (initialURL != null) { - // determine base URL (host name and initial context path) - WebDavResource base = new WebDavResource(httpClient, - initialURL, - serverInfo.getUserName(), serverInfo.getPassword(), serverInfo.isAuthPreemptive()); - - // look for well-known service (RFC 5785) - try { - WebDavResource wellKnown = new WebDavResource(base, "/.well-known/" + serviceName); - wellKnown.propfind(Mode.CURRENT_USER_PRINCIPAL); - if (wellKnown.getCurrentUserPrincipal() != null) - return new WebDavResource(wellKnown, wellKnown.getCurrentUserPrincipal()); - } catch (NotAuthorizedException e) { - Log.w(TAG, "Not authorized for well-known " + serviceName + " service detection", e); - throw e; - } catch (URISyntaxException e) { - Log.w(TAG, "Well-known" + serviceName + " service detection failed because of invalid URIs", e); - } catch (HttpException e) { - Log.d(TAG, "Well-known " + serviceName + " service detection failed with HTTP error", e); - } catch (DavException e) { - Log.w(TAG, "Well-known " + serviceName + " service detection failed with unexpected DAV response", e); - } - - // fall back to user-given initial context path - try { - base.propfind(Mode.CURRENT_USER_PRINCIPAL); - if (base.getCurrentUserPrincipal() != null) - return new WebDavResource(base, base.getCurrentUserPrincipal()); - } catch (NotAuthorizedException e) { - Log.e(TAG, "Not authorized for querying principal", e); - throw e; - } catch (HttpException e) { - Log.e(TAG, "HTTP error when querying principal", e); - } catch (DavException e) { - Log.e(TAG, "DAV error when querying principal", e); - } - Log.i(TAG, "Couldn't find current-user-principal for service " + serviceName); - } - return null; - } - - public static boolean checkHomesetCapabilities(WebDavResource resource, String davCapability) throws URISyntaxException, IOException { - // check for necessary capabilities - try { - resource.options(); - if (resource.supportsDAV(davCapability) && - resource.supportsMethod("PROPFIND")) // check only for methods that MUST be available for home sets - return true; - } catch(HttpException e) { - // for instance, 405 Method not allowed - } - return false; - } - - - SRVRecord selectSRVRecord(Record[] records) { - if (records.length > 1) - Log.w(TAG, "Multiple SRV records not supported yet; using first one"); - return (SRVRecord)records[0]; - } - -} diff --git a/src/at/bitfire/davdroid/resource/Event.java b/src/at/bitfire/davdroid/resource/Event.java deleted file mode 100644 index 91f68012..00000000 --- a/src/at/bitfire/davdroid/resource/Event.java +++ /dev/null @@ -1,368 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringReader; -import java.util.Calendar; -import java.util.LinkedList; -import java.util.List; -import java.util.SimpleTimeZone; -import java.util.TimeZone; - -import lombok.Getter; -import lombok.NonNull; -import lombok.Setter; -import net.fortuna.ical4j.data.CalendarBuilder; -import net.fortuna.ical4j.data.CalendarOutputter; -import net.fortuna.ical4j.data.ParserException; -import net.fortuna.ical4j.model.Component; -import net.fortuna.ical4j.model.ComponentList; -import net.fortuna.ical4j.model.Date; -import net.fortuna.ical4j.model.DateTime; -import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory; -import net.fortuna.ical4j.model.Property; -import net.fortuna.ical4j.model.PropertyList; -import net.fortuna.ical4j.model.TimeZoneRegistry; -import net.fortuna.ical4j.model.ValidationException; -import net.fortuna.ical4j.model.component.VAlarm; -import net.fortuna.ical4j.model.component.VEvent; -import net.fortuna.ical4j.model.component.VTimeZone; -import net.fortuna.ical4j.model.parameter.Value; -import net.fortuna.ical4j.model.property.Attendee; -import net.fortuna.ical4j.model.property.Clazz; -import net.fortuna.ical4j.model.property.DateProperty; -import net.fortuna.ical4j.model.property.Description; -import net.fortuna.ical4j.model.property.DtEnd; -import net.fortuna.ical4j.model.property.DtStart; -import net.fortuna.ical4j.model.property.Duration; -import net.fortuna.ical4j.model.property.ExDate; -import net.fortuna.ical4j.model.property.ExRule; -import net.fortuna.ical4j.model.property.LastModified; -import net.fortuna.ical4j.model.property.Location; -import net.fortuna.ical4j.model.property.Organizer; -import net.fortuna.ical4j.model.property.ProdId; -import net.fortuna.ical4j.model.property.RDate; -import net.fortuna.ical4j.model.property.RRule; -import net.fortuna.ical4j.model.property.Status; -import net.fortuna.ical4j.model.property.Summary; -import net.fortuna.ical4j.model.property.Transp; -import net.fortuna.ical4j.model.property.Uid; -import net.fortuna.ical4j.model.property.Version; -import net.fortuna.ical4j.util.SimpleHostInfo; -import net.fortuna.ical4j.util.UidGenerator; -import android.text.format.Time; -import android.util.Log; -import at.bitfire.davdroid.Constants; -import at.bitfire.davdroid.syncadapter.DavSyncAdapter; - - -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 private String summary, location, description; - - @Getter private DtStart dtStart; - @Getter private DtEnd dtEnd; - @Getter @Setter private Duration duration; - @Getter @Setter private RDate rdate; - @Getter @Setter private RRule rrule; - @Getter @Setter private ExDate exdate; - @Getter @Setter private ExRule exrule; - - @Getter @Setter private Boolean forPublic; - @Getter @Setter private Status status; - - @Getter @Setter private boolean opaque; - - @Getter @Setter private Organizer organizer; - @Getter private List attendees = new LinkedList(); - public void addAttendee(Attendee attendee) { - attendees.add(attendee); - } - - @Getter private List alarms = new LinkedList(); - public void addAlarm(VAlarm alarm) { - alarms.add(alarm); - } - - - public Event(String name, String ETag) { - super(name, ETag); - } - - public Event(long localID, String name, String ETag) { - super(localID, name, ETag); - } - - - @Override - public void initialize() { - generateUID(); - name = uid.replace("@", "_") + ".ics"; - } - - protected void generateUID() { - UidGenerator generator = new UidGenerator(new SimpleHostInfo(DavSyncAdapter.getAndroidID()), String.valueOf(android.os.Process.myPid())); - uid = generator.generateUid().getValue(); - } - - - @Override - @SuppressWarnings("unchecked") - public void parseEntity(@NonNull InputStream entity) throws IOException, InvalidResourceException { - net.fortuna.ical4j.model.Calendar ical; - try { - CalendarBuilder builder = new CalendarBuilder(); - ical = builder.build(entity); - - if (ical == null) - throw new InvalidResourceException("No iCalendar found"); - } catch (ParserException e) { - throw new InvalidResourceException(e); - } - - // event - ComponentList events = ical.getComponents(Component.VEVENT); - if (events == null || events.isEmpty()) - throw new InvalidResourceException("No VEVENT found"); - VEvent event = (VEvent)events.get(0); - - if (event.getUid() != null) - uid = event.getUid().getValue(); - else { - Log.w(TAG, "Received VEVENT without UID, generating new one"); - generateUID(); - } - - if ((dtStart = event.getStartDate()) == null || (dtEnd = event.getEndDate()) == null) - throw new InvalidResourceException("Invalid start time/end time/duration"); - - if (hasTime(dtStart)) { - validateTimeZone(dtStart); - validateTimeZone(dtEnd); - } - - // all-day events and "events on that day": - // * related UNIX times must be in UTC - // * must have a duration (set to one day if missing) - if (!hasTime(dtStart) && !dtEnd.getDate().after(dtStart.getDate())) { - Log.i(TAG, "Repairing iCal: DTEND := DTSTART+1"); - Calendar c = Calendar.getInstance(TimeZone.getTimeZone(Time.TIMEZONE_UTC)); - c.setTime(dtStart.getDate()); - c.add(Calendar.DATE, 1); - dtEnd.setDate(new Date(c.getTimeInMillis())); - } - - rrule = (RRule)event.getProperty(Property.RRULE); - rdate = (RDate)event.getProperty(Property.RDATE); - exrule = (ExRule)event.getProperty(Property.EXRULE); - exdate = (ExDate)event.getProperty(Property.EXDATE); - - if (event.getSummary() != null) - summary = event.getSummary().getValue(); - if (event.getLocation() != null) - location = event.getLocation().getValue(); - if (event.getDescription() != null) - description = event.getDescription().getValue(); - - status = event.getStatus(); - - opaque = true; - if (event.getTransparency() == Transp.TRANSPARENT) - opaque = false; - - organizer = event.getOrganizer(); - for (Object o : event.getProperties(Property.ATTENDEE)) - attendees.add((Attendee)o); - - Clazz classification = event.getClassification(); - if (classification != null) { - if (classification == Clazz.PUBLIC) - forPublic = true; - else if (classification == Clazz.CONFIDENTIAL || classification == Clazz.PRIVATE) - forPublic = false; - } - - this.alarms = event.getAlarms(); - } - - @Override - @SuppressWarnings("unchecked") - public ByteArrayOutputStream toEntity() throws IOException { - net.fortuna.ical4j.model.Calendar ical = new net.fortuna.ical4j.model.Calendar(); - ical.getProperties().add(Version.VERSION_2_0); - ical.getProperties().add(new ProdId("-//bitfire web engineering//DAVdroid " + Constants.APP_VERSION + " (ical4j 1.0.x)//EN")); - - VEvent event = new VEvent(); - PropertyList props = event.getProperties(); - - if (uid != null) - props.add(new Uid(uid)); - - props.add(dtStart); - if (dtEnd != null) - props.add(dtEnd); - if (duration != null) - props.add(duration); - - if (rrule != null) - props.add(rrule); - if (rdate != null) - props.add(rdate); - if (exrule != null) - props.add(exrule); - if (exdate != null) - props.add(exdate); - - if (summary != null && !summary.isEmpty()) - props.add(new Summary(summary)); - if (location != null && !location.isEmpty()) - props.add(new Location(location)); - if (description != null && !description.isEmpty()) - props.add(new Description(description)); - - if (status != null) - props.add(status); - if (!opaque) - props.add(Transp.TRANSPARENT); - - if (organizer != null) - props.add(organizer); - props.addAll(attendees); - - if (forPublic != null) - event.getProperties().add(forPublic ? Clazz.PUBLIC : Clazz.PRIVATE); - - event.getAlarms().addAll(alarms); - - props.add(new LastModified()); - ical.getComponents().add(event); - - // add VTIMEZONE components - net.fortuna.ical4j.model.TimeZone - tzStart = (dtStart == null ? null : dtStart.getTimeZone()), - tzEnd = (dtEnd == null ? null : dtEnd.getTimeZone()); - if (tzStart != null) - ical.getComponents().add(tzStart.getVTimeZone()); - if (tzEnd != null && tzEnd != tzStart) - ical.getComponents().add(tzEnd.getVTimeZone()); - - CalendarOutputter output = new CalendarOutputter(false); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - try { - output.output(ical, os); - } catch (ValidationException e) { - Log.e(TAG, "Generated invalid iCalendar"); - } - return os; - } - - - public long getDtStartInMillis() { - return dtStart.getDate().getTime(); - } - - public String getDtStartTzID() { - return getTzId(dtStart); - } - - public void setDtStart(long tsStart, String tzID) { - if (tzID == null) { // all-day - dtStart = new DtStart(new Date(tsStart)); - } else { - DateTime start = new DateTime(tsStart); - start.setTimeZone(tzRegistry.getTimeZone(tzID)); - dtStart = new DtStart(start); - } - } - - - public long getDtEndInMillis() { - return dtEnd.getDate().getTime(); - } - - public String getDtEndTzID() { - return getTzId(dtEnd); - } - - public void setDtEnd(long tsEnd, String tzID) { - if (tzID == null) { // all-day - dtEnd = new DtEnd(new Date(tsEnd)); - } else { - DateTime end = new DateTime(tsEnd); - end.setTimeZone(tzRegistry.getTimeZone(tzID)); - dtEnd = new DtEnd(end); - } - } - - - // helpers - - public boolean isAllDay() { - return !hasTime(dtStart); - } - - protected static boolean hasTime(DateProperty date) { - return date.getDate() instanceof DateTime; - } - - protected static String getTzId(DateProperty date) { - if (date.isUtc() || !hasTime(date)) - return Time.TIMEZONE_UTC; - else if (date.getTimeZone() != null) - return date.getTimeZone().getID(); - else if (date.getParameter(Value.TZID) != null) - return date.getParameter(Value.TZID).getValue(); - - // fallback - return Time.TIMEZONE_UTC; - } - - /* guess matching Android timezone ID */ - protected static void validateTimeZone(DateProperty date) { - if (date.isUtc() || !hasTime(date)) - return; - - String tzID = getTzId(date); - if (tzID == null) - return; - - String localTZ = Time.TIMEZONE_UTC; - - String availableTZs[] = SimpleTimeZone.getAvailableIDs(); - for (String availableTZ : availableTZs) - if (tzID.indexOf(availableTZ, 0) != -1) { - localTZ = availableTZ; - break; - } - - Log.d(TAG, "Assuming time zone " + localTZ + " for " + tzID); - date.setTimeZone(tzRegistry.getTimeZone(localTZ)); - } - - public static String TimezoneDefToTzId(String timezoneDef) throws IllegalArgumentException { - try { - if (timezoneDef != null) { - CalendarBuilder builder = new CalendarBuilder(); - net.fortuna.ical4j.model.Calendar cal = builder.build(new StringReader(timezoneDef)); - VTimeZone timezone = (VTimeZone)cal.getComponent(VTimeZone.VTIMEZONE); - return timezone.getTimeZoneId().getValue(); - } - } catch (Exception ex) { - Log.w(TAG, "Can't understand time zone definition, ignoring", ex); - } - throw new IllegalArgumentException(); - } -} diff --git a/src/at/bitfire/davdroid/resource/InvalidResourceException.java b/src/at/bitfire/davdroid/resource/InvalidResourceException.java deleted file mode 100644 index ee24e1f6..00000000 --- a/src/at/bitfire/davdroid/resource/InvalidResourceException.java +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -public class InvalidResourceException extends Exception { - private static final long serialVersionUID = 1593585432655578220L; - - public InvalidResourceException(String message) { - super(message); - } - - public InvalidResourceException(Throwable throwable) { - super(throwable); - } -} diff --git a/src/at/bitfire/davdroid/resource/LocalAddressBook.java b/src/at/bitfire/davdroid/resource/LocalAddressBook.java deleted file mode 100644 index b8711cbb..00000000 --- a/src/at/bitfire/davdroid/resource/LocalAddressBook.java +++ /dev/null @@ -1,1007 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -import java.io.IOException; -import java.io.InputStream; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -import lombok.Cleanup; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.WordUtils; - -import android.accounts.Account; -import android.content.ContentProviderClient; -import android.content.ContentProviderOperation; -import android.content.ContentProviderOperation.Builder; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.res.AssetFileDescriptor; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.net.Uri; -import android.os.RemoteException; -import android.provider.ContactsContract.CommonDataKinds; -import android.provider.ContactsContract.CommonDataKinds.Email; -import android.provider.ContactsContract.CommonDataKinds.GroupMembership; -import android.provider.ContactsContract.CommonDataKinds.Im; -import android.provider.ContactsContract.CommonDataKinds.Nickname; -import android.provider.ContactsContract.CommonDataKinds.Note; -import android.provider.ContactsContract.CommonDataKinds.Organization; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.CommonDataKinds.Photo; -import android.provider.ContactsContract.CommonDataKinds.SipAddress; -import android.provider.ContactsContract.CommonDataKinds.StructuredName; -import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; -import android.provider.ContactsContract.CommonDataKinds.Website; -import android.provider.ContactsContract.Data; -import android.provider.ContactsContract.Groups; -import android.provider.ContactsContract.RawContacts; -import android.util.Log; -import at.bitfire.davdroid.syncadapter.AccountSettings; -import ezvcard.parameter.AddressType; -import ezvcard.parameter.EmailType; -import ezvcard.parameter.ImppType; -import ezvcard.parameter.TelephoneType; -import ezvcard.property.Address; -import ezvcard.property.Anniversary; -import ezvcard.property.Birthday; -import ezvcard.property.DateOrTimeProperty; -import ezvcard.property.Impp; -import ezvcard.property.Telephone; - - -public class LocalAddressBook extends LocalCollection { - private final static String TAG = "davdroid.LocalAddressBook"; - - protected final static String COLUMN_UNKNOWN_PROPERTIES = RawContacts.SYNC3; - - - protected AccountSettings accountSettings; - - - /* database fields */ - - @Override - protected Uri entriesURI() { - return syncAdapterURI(RawContacts.CONTENT_URI); - } - - protected String entryColumnAccountType() { return RawContacts.ACCOUNT_TYPE; } - protected String entryColumnAccountName() { return RawContacts.ACCOUNT_NAME; } - - protected String entryColumnParentID() { return null; /* maybe use RawContacts.DATA_SET some day? */ } - protected String entryColumnID() { return RawContacts._ID; } - protected String entryColumnRemoteName() { return RawContacts.SOURCE_ID; } - protected String entryColumnETag() { return RawContacts.SYNC2; } - - protected String entryColumnDirty() { return RawContacts.DIRTY; } - protected String entryColumnDeleted() { return RawContacts.DELETED; } - - protected String entryColumnUID() { return RawContacts.SYNC1; } - - - - public LocalAddressBook(Account account, ContentProviderClient providerClient, AccountSettings accountSettings) { - super(account, providerClient); - this.accountSettings = accountSettings; - } - - - /* collection operations */ - - @Override - public long getId() { - return -1; - } - - @Override - public String getCTag() { - return accountSettings.getAddressBookCTag(); - } - - @Override - public void setCTag(String cTag) { - accountSettings.setAddressBookCTag(cTag); - } - - - /* create/update/delete */ - - public Contact newResource(long localID, String resourceName, String eTag) { - return new Contact(localID, resourceName, eTag); - } - - public void deleteAllExceptRemoteNames(Resource[] remoteResources) { - String where; - - if (remoteResources.length != 0) { - List sqlFileNames = new LinkedList(); - for (Resource res : remoteResources) - sqlFileNames.add(DatabaseUtils.sqlEscapeString(res.getName())); - where = entryColumnRemoteName() + " NOT IN (" + StringUtils.join(sqlFileNames, ",") + ")"; - } else - where = entryColumnRemoteName() + " IS NOT NULL"; - - Builder builder = ContentProviderOperation.newDelete(entriesURI()).withSelection(where, null); - pendingOperations.add(builder - .withYieldAllowed(true) - .build()); - } - - @Override - public void commit() throws LocalStorageException { - super.commit(); - - // update group details for groups we have just created - Uri groupsUri = syncAdapterURI(Groups.CONTENT_URI); - try { - // newly created groups don't have a TITLE - @Cleanup Cursor cursor = providerClient.query(groupsUri, - new String[] { Groups.SOURCE_ID }, - Groups.TITLE + " IS NULL", null, null - ); - while (cursor != null && cursor.moveToNext()) { - // found group, set TITLE to SOURCE_ID and other details - String sourceID = cursor.getString(0); - pendingOperations.add(ContentProviderOperation.newUpdate(groupsUri) - .withSelection(Groups.SOURCE_ID + "=?", new String[] { sourceID }) - .withValue(Groups.TITLE, sourceID) - .withValue(Groups.GROUP_VISIBLE, 1) - .build()); - super.commit(); - } - } catch (RemoteException e) { - throw new LocalStorageException("Couldn't update group names", e); - } - } - - - /* methods for populating the data object from the content provider */ - - @Override - public void populate(Resource res) throws LocalStorageException { - Contact c = (Contact)res; - - try { - @Cleanup Cursor cursor = providerClient.query(ContentUris.withAppendedId(entriesURI(), c.getLocalID()), - new String[] { entryColumnUID(), COLUMN_UNKNOWN_PROPERTIES, RawContacts.STARRED }, null, null, null); - if (cursor != null && cursor.moveToNext()) { - c.setUid(cursor.getString(0)); - c.setUnknownProperties(cursor.getString(1)); - c.setStarred(cursor.getInt(2) != 0); - } else - throw new RecordNotFoundException(); - - populateStructuredName(c); - populatePhoneNumbers(c); - populateEmailAddresses(c); - populatePhoto(c); - populateOrganization(c); - populateIMPPs(c); - populateNickname(c); - populateNote(c); - populatePostalAddresses(c); - populateCategories(c); - populateURLs(c); - populateEvents(c); - populateSipAddress(c); - } catch(RemoteException ex) { - throw new LocalStorageException(ex); - } - } - - private void populateStructuredName(Contact c) throws RemoteException { - @Cleanup Cursor cursor = providerClient.query(dataURI(), new String[] { - /* 0 */ StructuredName.DISPLAY_NAME, StructuredName.PREFIX, StructuredName.GIVEN_NAME, - /* 3 */ StructuredName.MIDDLE_NAME, StructuredName.FAMILY_NAME, StructuredName.SUFFIX, - /* 6 */ StructuredName.PHONETIC_GIVEN_NAME, StructuredName.PHONETIC_MIDDLE_NAME, StructuredName.PHONETIC_FAMILY_NAME - }, StructuredName.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), StructuredName.CONTENT_ITEM_TYPE }, null); - - if (cursor != null && cursor.moveToNext()) { - c.setDisplayName(cursor.getString(0)); - - c.setPrefix(cursor.getString(1)); - c.setGivenName(cursor.getString(2)); - c.setMiddleName(cursor.getString(3)); - c.setFamilyName(cursor.getString(4)); - c.setSuffix(cursor.getString(5)); - - c.setPhoneticGivenName(cursor.getString(6)); - c.setPhoneticMiddleName(cursor.getString(7)); - c.setPhoneticFamilyName(cursor.getString(8)); - } - } - - protected void populatePhoneNumbers(Contact c) throws RemoteException { - @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()) { - ezvcard.property.Telephone number = new ezvcard.property.Telephone(cursor.getString(2)); - switch (cursor.getInt(0)) { - case Phone.TYPE_HOME: - number.addType(TelephoneType.HOME); - break; - case Phone.TYPE_MOBILE: - number.addType(TelephoneType.CELL); - break; - case Phone.TYPE_WORK: - number.addType(TelephoneType.WORK); - break; - case Phone.TYPE_FAX_WORK: - number.addType(TelephoneType.FAX); - number.addType(TelephoneType.WORK); - break; - case Phone.TYPE_FAX_HOME: - number.addType(TelephoneType.FAX); - number.addType(TelephoneType.HOME); - break; - case Phone.TYPE_PAGER: - number.addType(TelephoneType.PAGER); - break; - case Phone.TYPE_CALLBACK: - number.addType(Contact.PHONE_TYPE_CALLBACK); - break; - case Phone.TYPE_CAR: - number.addType(TelephoneType.CAR); - break; - case Phone.TYPE_COMPANY_MAIN: - number.addType(Contact.PHONE_TYPE_COMPANY_MAIN); - break; - case Phone.TYPE_ISDN: - number.addType(TelephoneType.ISDN); - break; - case Phone.TYPE_MAIN: - number.addType(TelephoneType.PREF); - break; - case Phone.TYPE_OTHER_FAX: - number.addType(TelephoneType.FAX); - break; - case Phone.TYPE_RADIO: - number.addType(Contact.PHONE_TYPE_RADIO); - break; - case Phone.TYPE_TELEX: - number.addType(TelephoneType.TEXTPHONE); - break; - case Phone.TYPE_TTY_TDD: - number.addType(TelephoneType.TEXT); - break; - case Phone.TYPE_WORK_MOBILE: - number.addType(TelephoneType.CELL); - number.addType(TelephoneType.WORK); - break; - case Phone.TYPE_WORK_PAGER: - number.addType(TelephoneType.PAGER); - number.addType(TelephoneType.WORK); - break; - case Phone.TYPE_ASSISTANT: - number.addType(Contact.PHONE_TYPE_ASSISTANT); - break; - case Phone.TYPE_MMS: - number.addType(Contact.PHONE_TYPE_MMS); - break; - case Phone.TYPE_CUSTOM: - String customType = cursor.getString(1); - 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, 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()) { - ezvcard.property.Email email = new ezvcard.property.Email(cursor.getString(1)); - switch (cursor.getInt(0)) { - case Email.TYPE_HOME: - email.addType(EmailType.HOME); - break; - case Email.TYPE_WORK: - email.addType(EmailType.WORK); - break; - case Email.TYPE_MOBILE: - email.addType(Contact.EMAIL_TYPE_MOBILE); - break; - case Email.TYPE_CUSTOM: - String customType = cursor.getString(2); - 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); - } - } - - protected void populatePhoto(Contact c) throws RemoteException { - @Cleanup Cursor cursor = providerClient.query(dataURI(), - new String[] { Photo.PHOTO_FILE_ID, Photo.PHOTO }, - Photo.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), Photo.CONTENT_ITEM_TYPE }, null); - if (cursor != null && cursor.moveToNext()) { - if (!cursor.isNull(0)) { - Uri photoUri = Uri.withAppendedPath( - ContentUris.withAppendedId(RawContacts.CONTENT_URI, c.getLocalID()), - RawContacts.DisplayPhoto.CONTENT_DIRECTORY); - try { - @Cleanup AssetFileDescriptor fd = providerClient.openAssetFile(photoUri, "r"); - @Cleanup InputStream is = fd.createInputStream(); - c.setPhoto(IOUtils.toByteArray(is)); - } catch(IOException ex) { - Log.w(TAG, "Couldn't read high-res contact photo", ex); - } - } else - c.setPhoto(cursor.getBlob(1)); - } - } - - protected void populateOrganization(Contact c) throws RemoteException { - @Cleanup Cursor cursor = providerClient.query(dataURI(), - new String[] { Organization.COMPANY, Organization.DEPARTMENT, Organization.TITLE, Organization.JOB_DESCRIPTION }, - Organization.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), Organization.CONTENT_ITEM_TYPE }, null); - if (cursor != null && cursor.moveToNext()) { - String company = cursor.getString(0), - department = cursor.getString(1), - title = cursor.getString(2), - role = cursor.getString(3); - if (!StringUtils.isEmpty(company) || !StringUtils.isEmpty(department)) { - ezvcard.property.Organization org = new ezvcard.property.Organization(); - if (!StringUtils.isEmpty(company)) - org.addValue(company); - if (!StringUtils.isEmpty(department)) - org.addValue(department); - c.setOrganization(org); - } - if (!StringUtils.isEmpty(title)) - c.setJobTitle(title); - if (!StringUtils.isEmpty(role)) - c.setJobDescription(role); - } - } - - protected void populateIMPPs(Contact c) throws RemoteException { - @Cleanup Cursor cursor = providerClient.query(dataURI(), new String[] { Im.DATA, Im.TYPE, Im.LABEL, Im.PROTOCOL, Im.CUSTOM_PROTOCOL }, - Im.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), Im.CONTENT_ITEM_TYPE }, null); - while (cursor != null && cursor.moveToNext()) { - String handle = cursor.getString(0); - - Impp impp = null; - switch (cursor.getInt(3)) { - case Im.PROTOCOL_AIM: - impp = Impp.aim(handle); - break; - case Im.PROTOCOL_MSN: - impp = Impp.msn(handle); - break; - case Im.PROTOCOL_YAHOO: - impp = Impp.yahoo(handle); - break; - case Im.PROTOCOL_SKYPE: - impp = Impp.skype(handle); - break; - case Im.PROTOCOL_QQ: - impp = new Impp("qq", handle); - break; - case Im.PROTOCOL_GOOGLE_TALK: - impp = new Impp("google-talk", handle); - break; - case Im.PROTOCOL_ICQ: - impp = Impp.icq(handle); - break; - case Im.PROTOCOL_JABBER: - impp = Impp.xmpp(handle); - break; - case Im.PROTOCOL_NETMEETING: - impp = new Impp("netmeeting", handle); - break; - case Im.PROTOCOL_CUSTOM: - impp = new Impp(cursor.getString(4), handle); - } - - if (impp != null) { - switch (cursor.getInt(1)) { - case Im.TYPE_HOME: - impp.addType(ImppType.HOME); - break; - case Im.TYPE_WORK: - impp.addType(ImppType.WORK); - break; - case Im.TYPE_CUSTOM: - String customType = cursor.getString(2); - if (!StringUtils.isEmpty(customType)) - impp.addType(ImppType.get(labelToXName(customType))); - } - c.getImpps().add(impp); - } - } - } - - protected void populateNickname(Contact c) throws RemoteException { - @Cleanup Cursor cursor = providerClient.query(dataURI(), new String[] { Nickname.NAME }, - Nickname.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), Nickname.CONTENT_ITEM_TYPE }, null); - if (cursor != null && cursor.moveToNext()) - c.setNickName(cursor.getString(0)); - } - - protected void populateNote(Contact c) throws RemoteException { - @Cleanup Cursor cursor = providerClient.query(dataURI(), new String[] { Note.NOTE }, - Note.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), Note.CONTENT_ITEM_TYPE }, null); - if (cursor != null && cursor.moveToNext()) - c.setNote(cursor.getString(0)); - } - - protected void populatePostalAddresses(Contact c) throws RemoteException { - @Cleanup Cursor cursor = providerClient.query(dataURI(), new String[] { - /* 0 */ StructuredPostal.FORMATTED_ADDRESS, StructuredPostal.TYPE, StructuredPostal.LABEL, - /* 3 */ StructuredPostal.STREET, StructuredPostal.POBOX, StructuredPostal.NEIGHBORHOOD, - /* 6 */ StructuredPostal.CITY, StructuredPostal.REGION, StructuredPostal.POSTCODE, - /* 9 */ StructuredPostal.COUNTRY - }, StructuredPostal.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), StructuredPostal.CONTENT_ITEM_TYPE }, null); - while (cursor != null && cursor.moveToNext()) { - Address address = new Address(); - - address.setLabel(cursor.getString(0)); - switch (cursor.getInt(1)) { - case StructuredPostal.TYPE_HOME: - address.addType(AddressType.HOME); - break; - case StructuredPostal.TYPE_WORK: - address.addType(AddressType.WORK); - break; - case StructuredPostal.TYPE_CUSTOM: - String customType = cursor.getString(2); - if (!StringUtils.isEmpty(customType)) - address.addType(AddressType.get(labelToXName(customType))); - break; - } - address.setStreetAddress(cursor.getString(3)); - address.setPoBox(cursor.getString(4)); - address.setExtendedAddress(cursor.getString(5)); - address.setLocality(cursor.getString(6)); - address.setRegion(cursor.getString(7)); - address.setPostalCode(cursor.getString(8)); - address.setCountry(cursor.getString(9)); - c.getAddresses().add(address); - } - } - - protected void populateCategories(Contact c) throws RemoteException { - @Cleanup Cursor cursorMemberships = providerClient.query(dataURI(), - new String[] { GroupMembership.GROUP_ROW_ID, GroupMembership.GROUP_SOURCE_ID }, - GroupMembership.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), GroupMembership.CONTENT_ITEM_TYPE }, null); - List categories = c.getCategories(); - while (cursorMemberships != null && cursorMemberships.moveToNext()) { - long rowID = cursorMemberships.getLong(0); - String sourceID = cursorMemberships.getString(1); - - // either a row ID or a source ID must be available - String where, whereArg; - if (sourceID == null) { - where = Groups._ID + "=?"; - whereArg = String.valueOf(rowID); - } else { - where = Groups.SOURCE_ID + "=?"; - whereArg = sourceID; - } - where += " AND " + Groups.DELETED + "=0"; // ignore deleted groups - Log.d(TAG, "Populating group from " + where + " " + whereArg); - - // fetch group - @Cleanup Cursor cursorGroups = providerClient.query(Groups.CONTENT_URI, - new String[] { Groups.TITLE }, - where, new String[] { whereArg }, null - ); - if (cursorGroups != null && cursorGroups.moveToNext()) { - String title = cursorGroups.getString(0); - - if (sourceID == null) { // Group wasn't created by DAVdroid - // SOURCE_ID IS NULL <=> _ID IS NOT NULL - Log.d(TAG, "Setting SOURCE_ID of non-DAVdroid group to title: " + title); - - ContentValues v = new ContentValues(1); - v.put(Groups.SOURCE_ID, title); - v.put(Groups.GROUP_IS_READ_ONLY, 0); - v.put(Groups.GROUP_VISIBLE, 1); - providerClient.update(syncAdapterURI(Groups.CONTENT_URI), v, Groups._ID + "=?", new String[] { String.valueOf(rowID) }); - - sourceID = title; - } - - // add group to CATEGORIES - if (sourceID != null) - categories.add(sourceID); - } else - Log.d(TAG, "Group not found (maybe deleted)"); - } - } - - protected void populateURLs(Contact c) throws RemoteException { - @Cleanup Cursor cursor = providerClient.query(dataURI(), new String[] { Website.URL }, - Website.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), Website.CONTENT_ITEM_TYPE }, null); - while (cursor != null && cursor.moveToNext()) - c.getURLs().add(cursor.getString(0)); - } - - protected void populateEvents(Contact c) throws RemoteException { - @Cleanup Cursor cursor = providerClient.query(dataURI(), new String[] { CommonDataKinds.Event.TYPE, CommonDataKinds.Event.START_DATE }, - Photo.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), CommonDataKinds.Event.CONTENT_ITEM_TYPE }, null); - while (cursor != null && cursor.moveToNext()) { - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd", Locale.US); - try { - Date date = formatter.parse(cursor.getString(1)); - switch (cursor.getInt(0)) { - case CommonDataKinds.Event.TYPE_ANNIVERSARY: - c.setAnniversary(new Anniversary(date)); - break; - case CommonDataKinds.Event.TYPE_BIRTHDAY: - c.setBirthDay(new Birthday(date)); - break; - } - } catch (ParseException e) { - Log.w(TAG, "Couldn't parse local birthday/anniversary date", e); - } - } - } - - protected void populateSipAddress(Contact c) throws RemoteException { - @Cleanup Cursor cursor = providerClient.query(dataURI(), - new String[] { SipAddress.SIP_ADDRESS, SipAddress.TYPE, SipAddress.LABEL }, - SipAddress.RAW_CONTACT_ID + "=? AND " + Data.MIMETYPE + "=?", - new String[] { String.valueOf(c.getLocalID()), SipAddress.CONTENT_ITEM_TYPE }, null); - if (cursor != null && cursor.moveToNext()) { - Impp impp = new Impp("sip:" + cursor.getString(0)); - switch (cursor.getInt(1)) { - case SipAddress.TYPE_HOME: - impp.addType(ImppType.HOME); - break; - case SipAddress.TYPE_WORK: - impp.addType(ImppType.WORK); - break; - case SipAddress.TYPE_CUSTOM: - String customType = cursor.getString(2); - if (!StringUtils.isEmpty(customType)) - impp.addType(ImppType.get(labelToXName(customType))); - } - c.getImpps().add(impp); - } - } - - - /* content builder methods */ - - @Override - protected Builder buildEntry(Builder builder, Resource resource) { - Contact contact = (Contact)resource; - - return builder - .withValue(RawContacts.ACCOUNT_NAME, account.name) - .withValue(RawContacts.ACCOUNT_TYPE, account.type) - .withValue(entryColumnRemoteName(), contact.getName()) - .withValue(entryColumnUID(), contact.getUid()) - .withValue(entryColumnETag(), contact.getETag()) - .withValue(COLUMN_UNKNOWN_PROPERTIES, contact.getUnknownProperties()) - .withValue(RawContacts.STARRED, contact.isStarred() ? 1 : 0); - } - - - @Override - protected void addDataRows(Resource resource, long localID, int backrefIdx) { - Contact contact = (Contact)resource; - - queueOperation(buildStructuredName(newDataInsertBuilder(localID, backrefIdx), contact)); - - for (Telephone number : contact.getPhoneNumbers()) - queueOperation(buildPhoneNumber(newDataInsertBuilder(localID, backrefIdx), number)); - - for (ezvcard.property.Email email : contact.getEmails()) - queueOperation(buildEmail(newDataInsertBuilder(localID, backrefIdx), email)); - - if (contact.getPhoto() != null) - queueOperation(buildPhoto(newDataInsertBuilder(localID, backrefIdx), contact.getPhoto())); - - queueOperation(buildOrganization(newDataInsertBuilder(localID, backrefIdx), contact)); - - for (Impp impp : contact.getImpps()) - queueOperation(buildIMPP(newDataInsertBuilder(localID, backrefIdx), impp)); - - if (contact.getNickName() != null) - queueOperation(buildNickName(newDataInsertBuilder(localID, backrefIdx), contact.getNickName())); - - if (contact.getNote() != null) - queueOperation(buildNote(newDataInsertBuilder(localID, backrefIdx), contact.getNote())); - - for (Address address : contact.getAddresses()) - queueOperation(buildAddress(newDataInsertBuilder(localID, backrefIdx), address)); - - for (String category : contact.getCategories()) - queueOperation(buildGroupMembership(newDataInsertBuilder(localID, backrefIdx), category)); - - for (String url : contact.getURLs()) - queueOperation(buildURL(newDataInsertBuilder(localID, backrefIdx), url)); - - // events - if (contact.getAnniversary() != null) - queueOperation(buildEvent(newDataInsertBuilder(localID, backrefIdx), contact.getAnniversary(), CommonDataKinds.Event.TYPE_ANNIVERSARY)); - if (contact.getBirthDay() != null) - queueOperation(buildEvent(newDataInsertBuilder(localID, backrefIdx), contact.getBirthDay(), CommonDataKinds.Event.TYPE_BIRTHDAY)); - - // TODO relations - - // SIP addresses are built by buildIMPP - } - - @Override - protected void removeDataRows(Resource resource) { - pendingOperations.add(ContentProviderOperation.newDelete(dataURI()) - .withSelection(Data.RAW_CONTACT_ID + "=?", - new String[] { String.valueOf(resource.getLocalID()) }).build()); - } - - - protected Builder buildStructuredName(Builder builder, Contact contact) { - return builder - .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) - .withValue(StructuredName.PREFIX, contact.getPrefix()) - .withValue(StructuredName.DISPLAY_NAME, contact.getDisplayName()) - .withValue(StructuredName.GIVEN_NAME, contact.getGivenName()) - .withValue(StructuredName.MIDDLE_NAME, contact.getMiddleName()) - .withValue(StructuredName.FAMILY_NAME, contact.getFamilyName()) - .withValue(StructuredName.SUFFIX, contact.getSuffix()) - .withValue(StructuredName.PHONETIC_GIVEN_NAME, contact.getPhoneticGivenName()) - .withValue(StructuredName.PHONETIC_MIDDLE_NAME, contact.getPhoneticMiddleName()) - .withValue(StructuredName.PHONETIC_FAMILY_NAME, contact.getPhoneticFamilyName()); - } - - 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)) - typeCode = Phone.TYPE_FAX_HOME; - else if (types.contains(TelephoneType.WORK)) - typeCode = Phone.TYPE_FAX_WORK; - else - typeCode = Phone.TYPE_OTHER_FAX; - } else if (types.contains(TelephoneType.CELL)) { - if (types.contains(TelephoneType.WORK)) - typeCode = Phone.TYPE_WORK_MOBILE; - else - typeCode = Phone.TYPE_MOBILE; - } else if (types.contains(TelephoneType.PAGER)) { - if (types.contains(TelephoneType.WORK)) - typeCode = Phone.TYPE_WORK_PAGER; - else - typeCode = Phone.TYPE_PAGER; - // types with 1:1 translation - } else if (types.contains(TelephoneType.HOME)) { - typeCode = Phone.TYPE_HOME; - } else if (types.contains(TelephoneType.WORK)) { - typeCode = Phone.TYPE_WORK; - } else if (types.contains(Contact.PHONE_TYPE_CALLBACK)) { - typeCode = Phone.TYPE_CALLBACK; - } else if (types.contains(TelephoneType.CAR)) { - typeCode = Phone.TYPE_CAR; - } else if (types.contains(Contact.PHONE_TYPE_COMPANY_MAIN)) { - typeCode = Phone.TYPE_COMPANY_MAIN; - } else if (types.contains(TelephoneType.ISDN)) { - typeCode = Phone.TYPE_ISDN; - } else if (types.contains(TelephoneType.PREF)) { - typeCode = Phone.TYPE_MAIN; - } else if (types.contains(Contact.PHONE_TYPE_RADIO)) { - typeCode = Phone.TYPE_RADIO; - } else if (types.contains(TelephoneType.TEXTPHONE)) { - typeCode = Phone.TYPE_TELEX; - } else if (types.contains(TelephoneType.TEXT)) { - typeCode = Phone.TYPE_TTY_TDD; - } else if (types.contains(Contact.PHONE_TYPE_ASSISTANT)) { - typeCode = Phone.TYPE_ASSISTANT; - } else if (types.contains(Contact.PHONE_TYPE_MMS)) { - typeCode = Phone.TYPE_MMS; - } else if (!types.isEmpty()) { - TelephoneType type = types.iterator().next(); - typeCode = Phone.TYPE_CUSTOM; - typeLabel = xNameToLabel(type.getValue()); - } - - builder = builder - .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE) - .withValue(Phone.NUMBER, number.getText()) - .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; - } - - 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.PREF) - is_primary = true; - else if (type == EmailType.HOME) - typeCode = Email.TYPE_HOME; - else if (type == EmailType.WORK) - typeCode = Email.TYPE_WORK; - else if (type == Contact.EMAIL_TYPE_MOBILE) - typeCode = Email.TYPE_MOBILE; - if (typeCode == 0) { - if (email.getTypes().isEmpty()) - typeCode = Email.TYPE_OTHER; - else { - typeCode = Email.TYPE_CUSTOM; - typeLabel = xNameToLabel(email.getTypes().iterator().next().getValue()); - } - } - - builder = builder - .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE) - .withValue(Email.ADDRESS, email.getValue()) - .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; - } - - protected Builder buildPhoto(Builder builder, byte[] photo) { - return builder - .withValue(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE) - .withValue(Photo.PHOTO, photo); - } - - protected Builder buildOrganization(Builder builder, Contact contact) { - if (contact.getOrganization() == null && contact.getJobTitle() == null && contact.getJobDescription() == null) - return null; - - ezvcard.property.Organization organization = contact.getOrganization(); - String company = null, department = null; - if (organization != null) { - Iterator org = organization.getValues().iterator(); - if (org.hasNext()) - company = org.next(); - if (org.hasNext()) - department = org.next(); - } - - return builder - .withValue(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE) - .withValue(Organization.COMPANY, company) - .withValue(Organization.DEPARTMENT, department) - .withValue(Organization.TITLE, contact.getJobTitle()) - .withValue(Organization.JOB_DESCRIPTION, contact.getJobDescription()); - } - - protected Builder buildIMPP(Builder builder, Impp impp) { - int typeCode = 0; - String typeLabel = null; - for (ImppType type : impp.getTypes()) - if (type == ImppType.HOME) - typeCode = Im.TYPE_HOME; - else if (type == ImppType.WORK || type == ImppType.BUSINESS) - typeCode = Im.TYPE_WORK; - if (typeCode == 0) - if (impp.getTypes().isEmpty()) - typeCode = Im.TYPE_OTHER; - else { - typeCode = Im.TYPE_CUSTOM; - typeLabel = xNameToLabel(impp.getTypes().iterator().next().getValue()); - } - - int protocolCode = 0; - String protocolLabel = null; - - String protocol = impp.getProtocol(); - if (protocol == null) { - Log.w(TAG, "Ignoring IMPP address without protocol"); - return null; - } - - // SIP addresses are IMPP entries in the VCard but locally stored in SipAddress rather than Im - boolean sipAddress = false; - - if (impp.isAim()) - protocolCode = Im.PROTOCOL_AIM; - else if (impp.isMsn()) - protocolCode = Im.PROTOCOL_MSN; - else if (impp.isYahoo()) - protocolCode = Im.PROTOCOL_YAHOO; - else if (impp.isSkype()) - protocolCode = Im.PROTOCOL_SKYPE; - else if (protocol.equalsIgnoreCase("qq")) - protocolCode = Im.PROTOCOL_QQ; - else if (protocol.equalsIgnoreCase("google-talk")) - protocolCode = Im.PROTOCOL_GOOGLE_TALK; - else if (impp.isIcq()) - protocolCode = Im.PROTOCOL_ICQ; - else if (impp.isXmpp() || protocol.equalsIgnoreCase("jabber")) - protocolCode = Im.PROTOCOL_JABBER; - else if (protocol.equalsIgnoreCase("netmeeting")) - protocolCode = Im.PROTOCOL_NETMEETING; - else if (protocol.equalsIgnoreCase("sip")) - sipAddress = true; - else { - protocolCode = Im.PROTOCOL_CUSTOM; - protocolLabel = protocol; - } - - if (sipAddress) - // save as SIP address - builder = builder - .withValue(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE) - .withValue(Im.DATA, impp.getHandle()) - .withValue(Im.TYPE, typeCode); - else { - // save as IM address - builder = builder - .withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE) - .withValue(Im.DATA, impp.getHandle()) - .withValue(Im.TYPE, typeCode) - .withValue(Im.PROTOCOL, protocolCode); - if (protocolLabel != null) - builder = builder.withValue(Im.CUSTOM_PROTOCOL, protocolLabel); - } - if (typeLabel != null) - builder = builder.withValue(Im.LABEL, typeLabel); - return builder; - } - - protected Builder buildNickName(Builder builder, String nickName) { - return builder - .withValue(Data.MIMETYPE, Nickname.CONTENT_ITEM_TYPE) - .withValue(Nickname.NAME, nickName); - } - - protected Builder buildNote(Builder builder, String note) { - return builder - .withValue(Data.MIMETYPE, Note.CONTENT_ITEM_TYPE) - .withValue(Note.NOTE, note); - } - - protected Builder buildAddress(Builder builder, Address address) { - /* street po.box (extended) - * region - * postal code city - * country - */ - String formattedAddress = address.getLabel(); - if (StringUtils.isEmpty(formattedAddress)) { - String lineStreet = StringUtils.join(new String[] { address.getStreetAddress(), address.getPoBox(), address.getExtendedAddress() }, " "), - lineLocality = StringUtils.join(new String[] { address.getPostalCode(), address.getLocality() }, " "); - - List lines = new LinkedList(); - if (lineStreet != null) - lines.add(lineStreet); - if (address.getRegion() != null && !address.getRegion().isEmpty()) - lines.add(address.getRegion()); - if (lineLocality != null) - lines.add(lineLocality); - - formattedAddress = StringUtils.join(lines, "\n"); - } - - int typeCode = 0; - String typeLabel = null; - for (AddressType type : address.getTypes()) - if (type == AddressType.HOME) - typeCode = StructuredPostal.TYPE_HOME; - else if (type == AddressType.WORK) - typeCode = StructuredPostal.TYPE_WORK; - if (typeCode == 0) - if (address.getTypes().isEmpty()) - typeCode = StructuredPostal.TYPE_OTHER; - else { - typeCode = StructuredPostal.TYPE_CUSTOM; - typeLabel = xNameToLabel(address.getTypes().iterator().next().getValue()); - } - - builder = builder - .withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE) - .withValue(StructuredPostal.FORMATTED_ADDRESS, formattedAddress) - .withValue(StructuredPostal.TYPE, typeCode) - .withValue(StructuredPostal.STREET, address.getStreetAddress()) - .withValue(StructuredPostal.POBOX, address.getPoBox()) - .withValue(StructuredPostal.NEIGHBORHOOD, address.getExtendedAddress()) - .withValue(StructuredPostal.CITY, address.getLocality()) - .withValue(StructuredPostal.REGION, address.getRegion()) - .withValue(StructuredPostal.POSTCODE, address.getPostalCode()) - .withValue(StructuredPostal.COUNTRY, address.getCountry()); - if (typeLabel != null) - builder = builder.withValue(StructuredPostal.LABEL, typeLabel); - return builder; - } - - protected Builder buildGroupMembership(Builder builder, String group) { - return builder - .withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE) - .withValue(GroupMembership.GROUP_SOURCE_ID, group); - } - - protected Builder buildURL(Builder builder, String url) { - return builder - .withValue(Data.MIMETYPE, Website.CONTENT_ITEM_TYPE) - .withValue(Website.URL, url); - } - - protected Builder buildEvent(Builder builder, DateOrTimeProperty date, int type) { - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd", Locale.US); - if (date.getDate() == null) { - Log.i(TAG, "Ignoring contact event without date"); - return null; - } - return builder - .withValue(Data.MIMETYPE, CommonDataKinds.Event.CONTENT_ITEM_TYPE) - .withValue(CommonDataKinds.Event.TYPE, type) - .withValue(CommonDataKinds.Event.START_DATE, formatter.format(date.getDate())); - } - - - - /* helper methods */ - - protected Uri dataURI() { - return syncAdapterURI(Data.CONTENT_URI); - } - - protected static String labelToXName(String label) { - return "X-" + label.replaceAll(" ","_").replaceAll("[^\\p{L}\\p{Nd}\\-_]", "").toUpperCase(Locale.US); - } - - private Builder newDataInsertBuilder(long raw_contact_id, Integer backrefIdx) { - return newDataInsertBuilder(dataURI(), Data.RAW_CONTACT_ID, raw_contact_id, backrefIdx); - } - - protected static String xNameToLabel(String xname) { - // "X-MY_PROPERTY" - // 1. ensure lower case -> "x-my_property" - // 2. remove x- from beginning -> "my_property" - // 3. replace "_" by " " -> "my property" - // 4. capitalize -> "My Property" - String lowerCase = StringUtils.lowerCase(xname, Locale.US), - withoutPrefix = StringUtils.removeStart(lowerCase, "x-"), - withSpaces = StringUtils.replace(withoutPrefix, "_", " "); - return WordUtils.capitalize(withSpaces); - } - -} diff --git a/src/at/bitfire/davdroid/resource/LocalCalendar.java b/src/at/bitfire/davdroid/resource/LocalCalendar.java deleted file mode 100644 index 9fb60064..00000000 --- a/src/at/bitfire/davdroid/resource/LocalCalendar.java +++ /dev/null @@ -1,612 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -import java.net.URI; -import java.net.URISyntaxException; -import java.text.ParseException; -import java.util.LinkedList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import lombok.Cleanup; -import lombok.Getter; -import net.fortuna.ical4j.model.Dur; -import net.fortuna.ical4j.model.Parameter; -import net.fortuna.ical4j.model.ParameterList; -import net.fortuna.ical4j.model.PropertyList; -import net.fortuna.ical4j.model.component.VAlarm; -import net.fortuna.ical4j.model.parameter.Cn; -import net.fortuna.ical4j.model.parameter.CuType; -import net.fortuna.ical4j.model.parameter.PartStat; -import net.fortuna.ical4j.model.parameter.Role; -import net.fortuna.ical4j.model.property.Action; -import net.fortuna.ical4j.model.property.Attendee; -import net.fortuna.ical4j.model.property.Description; -import net.fortuna.ical4j.model.property.Duration; -import net.fortuna.ical4j.model.property.ExDate; -import net.fortuna.ical4j.model.property.ExRule; -import net.fortuna.ical4j.model.property.Organizer; -import net.fortuna.ical4j.model.property.RDate; -import net.fortuna.ical4j.model.property.RRule; -import net.fortuna.ical4j.model.property.Status; - -import org.apache.commons.lang.StringUtils; - -import android.accounts.Account; -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.ContentProviderClient; -import android.content.ContentProviderOperation; -import android.content.ContentProviderOperation.Builder; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.net.Uri; -import android.os.Build; -import android.os.RemoteException; -import android.provider.CalendarContract; -import android.provider.CalendarContract.Attendees; -import android.provider.CalendarContract.Calendars; -import android.provider.CalendarContract.Events; -import android.provider.CalendarContract.Reminders; -import android.provider.ContactsContract; -import android.util.Log; - -/** - * Represents a locally stored calendar, containing Events. - * Communicates with the Android Contacts Provider which uses an SQLite - * database to store the contacts. - */ -public class LocalCalendar extends LocalCollection { - private static final String TAG = "davdroid.LocalCalendar"; - - @Getter protected long id; - @Getter protected String url; - - protected static String COLLECTION_COLUMN_CTAG = Calendars.CAL_SYNC1; - - - /* database fields */ - - @Override - protected Uri entriesURI() { - return syncAdapterURI(Events.CONTENT_URI); - } - - protected String entryColumnAccountType() { return Events.ACCOUNT_TYPE; } - protected String entryColumnAccountName() { return Events.ACCOUNT_NAME; } - - protected String entryColumnParentID() { return Events.CALENDAR_ID; } - protected String entryColumnID() { return Events._ID; } - protected String entryColumnRemoteName() { return Events._SYNC_ID; } - protected String entryColumnETag() { return Events.SYNC_DATA1; } - - protected String entryColumnDirty() { return Events.DIRTY; } - protected String entryColumnDeleted() { return Events.DELETED; } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - protected String entryColumnUID() { - return (android.os.Build.VERSION.SDK_INT >= 17) ? - Events.UID_2445 : Events.SYNC_DATA2; - } - - - /* class methods, constructor */ - - @SuppressLint("InlinedApi") - public static void create(Account account, ContentResolver resolver, ServerInfo.ResourceInfo info) throws LocalStorageException { - ContentProviderClient client = resolver.acquireContentProviderClient(CalendarContract.AUTHORITY); - if (client == null) - throw new LocalStorageException("No Calendar Provider found (Calendar app disabled?)"); - - int color = 0xFFC3EA6E; // fallback: "DAVdroid green" - if (info.getColor() != null) { - Pattern p = Pattern.compile("#?(\\p{XDigit}{6})(\\p{XDigit}{2})?"); - Matcher m = p.matcher(info.getColor()); - if (m.find()) { - int color_rgb = Integer.parseInt(m.group(1), 16); - int color_alpha = m.group(2) != null ? (Integer.parseInt(m.group(2), 16) & 0xFF) : 0xFF; - color = (color_alpha << 24) | color_rgb; - } - } - - ContentValues values = new ContentValues(); - values.put(Calendars.ACCOUNT_NAME, account.name); - values.put(Calendars.ACCOUNT_TYPE, account.type); - values.put(Calendars.NAME, info.getURL()); - values.put(Calendars.CALENDAR_DISPLAY_NAME, info.getTitle()); - values.put(Calendars.CALENDAR_COLOR, color); - values.put(Calendars.OWNER_ACCOUNT, account.name); - values.put(Calendars.SYNC_EVENTS, 1); - values.put(Calendars.VISIBLE, 1); - values.put(Calendars.ALLOWED_REMINDERS, Reminders.METHOD_ALERT); - - if (info.isReadOnly()) - values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_READ); - else { - values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER); - values.put(Calendars.CAN_ORGANIZER_RESPOND, 1); - values.put(Calendars.CAN_MODIFY_TIME_ZONE, 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()); - - Log.i(TAG, "Inserting calendar: " + values.toString() + " -> " + calendarsURI(account).toString()); - try { - client.insert(calendarsURI(account), values); - } catch(RemoteException e) { - throw new LocalStorageException(e); - } - } - - public static LocalCalendar[] findAll(Account account, ContentProviderClient providerClient) throws RemoteException { - @Cleanup Cursor cursor = providerClient.query(calendarsURI(account), - new String[] { Calendars._ID, Calendars.NAME }, - Calendars.DELETED + "=0 AND " + Calendars.SYNC_EVENTS + "=1", null, null); - - LinkedList calendars = new LinkedList(); - while (cursor != null && cursor.moveToNext()) - calendars.add(new LocalCalendar(account, providerClient, cursor.getInt(0), cursor.getString(1))); - return calendars.toArray(new LocalCalendar[0]); - } - - public LocalCalendar(Account account, ContentProviderClient providerClient, long id, String url) throws RemoteException { - super(account, providerClient); - this.id = id; - this.url = url; - } - - - /* collection operations */ - - @Override - public String getCTag() throws LocalStorageException { - try { - @Cleanup Cursor c = providerClient.query(ContentUris.withAppendedId(calendarsURI(), id), - new String[] { COLLECTION_COLUMN_CTAG }, null, null, null); - if (c.moveToFirst()) { - return c.getString(0); - } else - throw new LocalStorageException("Couldn't query calendar CTag"); - } catch(RemoteException e) { - throw new LocalStorageException(e); - } - } - - @Override - public void setCTag(String cTag) throws LocalStorageException { - ContentValues values = new ContentValues(1); - values.put(COLLECTION_COLUMN_CTAG, cTag); - try { - providerClient.update(ContentUris.withAppendedId(calendarsURI(), id), values, null, null); - } catch(RemoteException e) { - throw new LocalStorageException(e); - } - } - - - /* create/update/delete */ - - public Event newResource(long localID, String resourceName, String eTag) { - return new Event(localID, resourceName, eTag); - } - - public void deleteAllExceptRemoteNames(Resource[] remoteResources) { - String where; - - if (remoteResources.length != 0) { - List sqlFileNames = new LinkedList(); - for (Resource res : remoteResources) - sqlFileNames.add(DatabaseUtils.sqlEscapeString(res.getName())); - where = entryColumnRemoteName() + " NOT IN (" + StringUtils.join(sqlFileNames, ",") + ")"; - } else - where = entryColumnRemoteName() + " IS NOT NULL"; - - Builder builder = ContentProviderOperation.newDelete(entriesURI()) - .withSelection(entryColumnParentID() + "=? AND (" + where + ")", new String[] { String.valueOf(id) }); - pendingOperations.add(builder - .withYieldAllowed(true) - .build()); - } - - - /* methods for populating the data object from the content provider */ - - - @Override - public void populate(Resource resource) throws LocalStorageException { - Event e = (Event)resource; - - try { - @Cleanup Cursor cursor = providerClient.query(ContentUris.withAppendedId(entriesURI(), e.getLocalID()), - new String[] { - /* 0 */ Events.TITLE, Events.EVENT_LOCATION, Events.DESCRIPTION, - /* 3 */ Events.DTSTART, Events.DTEND, Events.EVENT_TIMEZONE, Events.EVENT_END_TIMEZONE, Events.ALL_DAY, - /* 8 */ Events.STATUS, Events.ACCESS_LEVEL, - /* 10 */ Events.RRULE, Events.RDATE, Events.EXRULE, Events.EXDATE, - /* 14 */ Events.HAS_ATTENDEE_DATA, Events.ORGANIZER, Events.SELF_ATTENDEE_STATUS, - /* 17 */ entryColumnUID(), Events.DURATION, Events.AVAILABILITY - }, null, null, null); - if (cursor != null && cursor.moveToNext()) { - e.setUid(cursor.getString(17)); - - e.setSummary(cursor.getString(0)); - e.setLocation(cursor.getString(1)); - e.setDescription(cursor.getString(2)); - - boolean allDay = cursor.getInt(7) != 0; - long tsStart = cursor.getLong(3), - tsEnd = cursor.getLong(4); - String duration = cursor.getString(18); - - String tzId = null; - if (allDay) { - e.setDtStart(tsStart, null); - // provide only DTEND and not DURATION for all-day events - if (tsEnd == 0) { - Dur dur = new Dur(duration); - java.util.Date dEnd = dur.getTime(new java.util.Date(tsStart)); - tsEnd = dEnd.getTime(); - } - e.setDtEnd(tsEnd, null); - - } else { - // use the start time zone for the end time, too - // because apps like Samsung Planner allow the user to change "the" time zone but change the start time zone only - tzId = cursor.getString(5); - e.setDtStart(tsStart, tzId); - if (tsEnd != 0) - e.setDtEnd(tsEnd, tzId); - else if (!StringUtils.isEmpty(duration)) - e.setDuration(new Duration(new Dur(duration))); - } - - // recurrence - try { - String strRRule = cursor.getString(10); - if (!StringUtils.isEmpty(strRRule)) - e.setRrule(new RRule(strRRule)); - - String strRDate = cursor.getString(11); - if (!StringUtils.isEmpty(strRDate)) { - RDate rDate = new RDate(); - rDate.setValue(strRDate); - e.setRdate(rDate); - } - - String strExRule = cursor.getString(12); - if (!StringUtils.isEmpty(strExRule)) { - ExRule exRule = new ExRule(); - exRule.setValue(strExRule); - e.setExrule(exRule); - } - - String strExDate = cursor.getString(13); - if (!StringUtils.isEmpty(strExDate)) { - // ignored, see https://code.google.com/p/android/issues/detail?id=21426 - ExDate exDate = new ExDate(); - exDate.setValue(strExDate); - e.setExdate(exDate); - } - } catch (ParseException ex) { - Log.w(TAG, "Couldn't parse recurrence rules, ignoring", ex); - } catch (IllegalArgumentException ex) { - Log.w(TAG, "Invalid recurrence rules, ignoring", ex); - } - - // status - switch (cursor.getInt(8)) { - case Events.STATUS_CONFIRMED: - e.setStatus(Status.VEVENT_CONFIRMED); - break; - case Events.STATUS_TENTATIVE: - e.setStatus(Status.VEVENT_TENTATIVE); - break; - case Events.STATUS_CANCELED: - e.setStatus(Status.VEVENT_CANCELLED); - } - - // availability - e.setOpaque(cursor.getInt(19) != Events.AVAILABILITY_FREE); - - // attendees - if (cursor.getInt(14) != 0) { // has attendees - try { - e.setOrganizer(new Organizer(new URI("mailto", cursor.getString(15), null))); - } catch (URISyntaxException ex) { - Log.e(TAG, "Error when creating ORGANIZER URI, ignoring", ex); - } - populateAttendees(e); - } - - // classification - switch (cursor.getInt(9)) { - case Events.ACCESS_CONFIDENTIAL: - case Events.ACCESS_PRIVATE: - e.setForPublic(false); - break; - case Events.ACCESS_PUBLIC: - e.setForPublic(true); - } - - populateReminders(e); - } else - throw new RecordNotFoundException(); - } catch(RemoteException ex) { - throw new LocalStorageException(ex); - } - } - - - void populateAttendees(Event e) throws RemoteException { - Uri attendeesUri = Attendees.CONTENT_URI.buildUpon() - .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true") - .build(); - @Cleanup Cursor c = providerClient.query(attendeesUri, new String[] { - /* 0 */ Attendees.ATTENDEE_EMAIL, Attendees.ATTENDEE_NAME, Attendees.ATTENDEE_TYPE, - /* 3 */ Attendees.ATTENDEE_RELATIONSHIP, Attendees.STATUS - }, Attendees.EVENT_ID + "=?", new String[] { String.valueOf(e.getLocalID()) }, null); - while (c != null && c.moveToNext()) { - try { - Attendee attendee = new Attendee(new URI("mailto", c.getString(0), null)); - ParameterList params = attendee.getParameters(); - - String cn = c.getString(1); - if (cn != null) - params.add(new Cn(cn)); - - // type - int type = c.getInt(2); - params.add((type == Attendees.TYPE_RESOURCE) ? CuType.RESOURCE : CuType.INDIVIDUAL); - - // role - int relationship = c.getInt(3); - switch (relationship) { - case Attendees.RELATIONSHIP_ORGANIZER: - params.add(Role.CHAIR); - break; - case Attendees.RELATIONSHIP_ATTENDEE: - case Attendees.RELATIONSHIP_PERFORMER: - case Attendees.RELATIONSHIP_SPEAKER: - params.add((type == Attendees.TYPE_REQUIRED) ? Role.REQ_PARTICIPANT : Role.OPT_PARTICIPANT); - break; - case Attendees.RELATIONSHIP_NONE: - params.add(Role.NON_PARTICIPANT); - } - - // status - switch (c.getInt(4)) { - case Attendees.ATTENDEE_STATUS_INVITED: - params.add(PartStat.NEEDS_ACTION); - break; - case Attendees.ATTENDEE_STATUS_ACCEPTED: - params.add(PartStat.ACCEPTED); - break; - case Attendees.ATTENDEE_STATUS_DECLINED: - params.add(PartStat.DECLINED); - break; - case Attendees.ATTENDEE_STATUS_TENTATIVE: - params.add(PartStat.TENTATIVE); - break; - } - - e.addAttendee(attendee); - } catch (URISyntaxException ex) { - Log.e(TAG, "Couldn't parse attendee information, ignoring", ex); - } - } - } - - void populateReminders(Event e) throws RemoteException { - // reminders - Uri remindersUri = Reminders.CONTENT_URI.buildUpon() - .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, "true") - .build(); - @Cleanup Cursor c = providerClient.query(remindersUri, new String[] { - /* 0 */ Reminders.MINUTES, Reminders.METHOD - }, Reminders.EVENT_ID + "=?", new String[] { String.valueOf(e.getLocalID()) }, null); - while (c != null && c.moveToNext()) { - VAlarm alarm = new VAlarm(new Dur(0, 0, -c.getInt(0), 0)); - - PropertyList props = alarm.getProperties(); - switch (c.getInt(1)) { - /*case Reminders.METHOD_EMAIL: - props.add(Action.EMAIL); - break;*/ - default: - props.add(Action.DISPLAY); - props.add(new Description(e.getSummary())); - } - e.addAlarm(alarm); - } - } - - - /* content builder methods */ - - @Override - protected Builder buildEntry(Builder builder, Resource resource) { - Event event = (Event)resource; - - builder = builder - .withValue(Events.CALENDAR_ID, id) - .withValue(entryColumnRemoteName(), event.getName()) - .withValue(entryColumnETag(), event.getETag()) - .withValue(entryColumnUID(), event.getUid()) - .withValue(Events.ALL_DAY, event.isAllDay() ? 1 : 0) - .withValue(Events.DTSTART, event.getDtStartInMillis()) - .withValue(Events.EVENT_TIMEZONE, event.getDtStartTzID()) - .withValue(Events.HAS_ATTENDEE_DATA, event.getAttendees().isEmpty() ? 0 : 1) - .withValue(Events.GUESTS_CAN_INVITE_OTHERS, 1) - .withValue(Events.GUESTS_CAN_MODIFY, 1) - .withValue(Events.GUESTS_CAN_SEE_GUESTS, 1); - - boolean recurring = false; - if (event.getRrule() != null) { - recurring = true; - builder = builder.withValue(Events.RRULE, event.getRrule().getValue()); - } - if (event.getRdate() != null) { - recurring = true; - builder = builder.withValue(Events.RDATE, event.getRdate().getValue()); - } - if (event.getExrule() != null) - builder = builder.withValue(Events.EXRULE, event.getExrule().getValue()); - if (event.getExdate() != null) - builder = builder.withValue(Events.EXDATE, event.getExdate().getValue()); - - // set either DTEND for single-time events or DURATION for recurring events - // because that's the way Android likes it (see docs) - if (recurring) { - // calculate DURATION from start and end date - Duration duration = new Duration(event.getDtStart().getDate(), event.getDtEnd().getDate()); - builder = builder.withValue(Events.DURATION, duration.getValue()); - } else { - builder = builder - .withValue(Events.DTEND, event.getDtEndInMillis()) - .withValue(Events.EVENT_END_TIMEZONE, event.getDtEndTzID()); - } - - if (event.getSummary() != null) - builder = builder.withValue(Events.TITLE, event.getSummary()); - if (event.getLocation() != null) - builder = builder.withValue(Events.EVENT_LOCATION, event.getLocation()); - if (event.getDescription() != null) - builder = builder.withValue(Events.DESCRIPTION, event.getDescription()); - - if (event.getOrganizer() != null && event.getOrganizer().getCalAddress() != null) { - URI organizer = event.getOrganizer().getCalAddress(); - if (organizer.getScheme() != null && organizer.getScheme().equalsIgnoreCase("mailto")) - builder = builder.withValue(Events.ORGANIZER, organizer.getSchemeSpecificPart()); - } - - Status status = event.getStatus(); - if (status != null) { - int statusCode = Events.STATUS_TENTATIVE; - if (status == Status.VEVENT_CONFIRMED) - statusCode = Events.STATUS_CONFIRMED; - else if (status == Status.VEVENT_CANCELLED) - statusCode = Events.STATUS_CANCELED; - builder = builder.withValue(Events.STATUS, statusCode); - } - - builder = builder.withValue(Events.AVAILABILITY, event.isOpaque() ? Events.AVAILABILITY_BUSY : Events.AVAILABILITY_FREE); - - if (event.getForPublic() != null) - builder = builder.withValue(Events.ACCESS_LEVEL, event.getForPublic() ? Events.ACCESS_PUBLIC : Events.ACCESS_PRIVATE); - - return builder; - } - - - @Override - protected void addDataRows(Resource resource, long localID, int backrefIdx) { - Event event = (Event)resource; - for (Attendee attendee : event.getAttendees()) - pendingOperations.add(buildAttendee(newDataInsertBuilder(Attendees.CONTENT_URI, Attendees.EVENT_ID, localID, backrefIdx), attendee).build()); - for (VAlarm alarm : event.getAlarms()) - pendingOperations.add(buildReminder(newDataInsertBuilder(Reminders.CONTENT_URI, Reminders.EVENT_ID, localID, backrefIdx), alarm).build()); - } - - @Override - protected void removeDataRows(Resource resource) { - Event event = (Event)resource; - pendingOperations.add(ContentProviderOperation.newDelete(syncAdapterURI(Attendees.CONTENT_URI)) - .withSelection(Attendees.EVENT_ID + "=?", - new String[] { String.valueOf(event.getLocalID()) }).build()); - pendingOperations.add(ContentProviderOperation.newDelete(syncAdapterURI(Reminders.CONTENT_URI)) - .withSelection(Reminders.EVENT_ID + "=?", - new String[] { String.valueOf(event.getLocalID()) }).build()); - } - - - @SuppressLint("InlinedApi") - protected Builder buildAttendee(Builder builder, Attendee attendee) { - Uri member = Uri.parse(attendee.getValue()); - String email = member.getSchemeSpecificPart(); - - Cn cn = (Cn)attendee.getParameter(Parameter.CN); - if (cn != null) - builder = builder.withValue(Attendees.ATTENDEE_NAME, cn.getValue()); - - int type = Attendees.TYPE_NONE; - - CuType cutype = (CuType)attendee.getParameter(Parameter.CUTYPE); - if (cutype == CuType.RESOURCE) - type = Attendees.TYPE_RESOURCE; - else { - Role role = (Role)attendee.getParameter(Parameter.ROLE); - int relationship; - if (role == Role.CHAIR) - relationship = Attendees.RELATIONSHIP_ORGANIZER; - else { - relationship = Attendees.RELATIONSHIP_ATTENDEE; - if (role == Role.OPT_PARTICIPANT) - type = Attendees.TYPE_OPTIONAL; - else if (role == Role.REQ_PARTICIPANT) - type = Attendees.TYPE_REQUIRED; - } - builder = builder.withValue(Attendees.ATTENDEE_RELATIONSHIP, relationship); - } - - int status = Attendees.ATTENDEE_STATUS_NONE; - PartStat partStat = (PartStat)attendee.getParameter(Parameter.PARTSTAT); - if (partStat == null || partStat == PartStat.NEEDS_ACTION) - status = Attendees.ATTENDEE_STATUS_INVITED; - else if (partStat == PartStat.ACCEPTED) - status = Attendees.ATTENDEE_STATUS_ACCEPTED; - else if (partStat == PartStat.DECLINED) - status = Attendees.ATTENDEE_STATUS_DECLINED; - else if (partStat == PartStat.TENTATIVE) - status = Attendees.ATTENDEE_STATUS_TENTATIVE; - - return builder - .withValue(Attendees.ATTENDEE_EMAIL, email) - .withValue(Attendees.ATTENDEE_TYPE, type) - .withValue(Attendees.ATTENDEE_STATUS, status); - } - - protected Builder buildReminder(Builder builder, VAlarm alarm) { - int minutes = 0; - - Dur duration; - if (alarm.getTrigger() != null && (duration = alarm.getTrigger().getDuration()) != null) - minutes = duration.getDays() * 24*60 + duration.getHours()*60 + duration.getMinutes(); - - Log.d(TAG, "Adding alarm " + minutes + " min before"); - - return builder - .withValue(Reminders.METHOD, Reminders.METHOD_ALERT) - .withValue(Reminders.MINUTES, minutes); - } - - - - /* private helper methods */ - - protected static Uri calendarsURI(Account account) { - return Calendars.CONTENT_URI.buildUpon().appendQueryParameter(Calendars.ACCOUNT_NAME, account.name) - .appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type) - .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true").build(); - } - - protected Uri calendarsURI() { - return calendarsURI(account); - } - -} diff --git a/src/at/bitfire/davdroid/resource/LocalCollection.java b/src/at/bitfire/davdroid/resource/LocalCollection.java deleted file mode 100644 index 24293421..00000000 --- a/src/at/bitfire/davdroid/resource/LocalCollection.java +++ /dev/null @@ -1,361 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -import java.util.ArrayList; - -import lombok.Cleanup; -import android.accounts.Account; -import android.content.ContentProviderClient; -import android.content.ContentProviderOperation; -import android.content.ContentProviderOperation.Builder; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.OperationApplicationException; -import android.database.Cursor; -import android.net.Uri; -import android.os.RemoteException; -import android.provider.CalendarContract; -import android.util.Log; - -/** - * Represents a locally-stored synchronizable collection (for instance, the - * address book or a calendar). Manages a CTag that stores the last known - * remote CTag (the remote CTag changes whenever something in the remote collection changes). - * - * @param Subtype of Resource that can be stored in the collection - */ -public abstract class LocalCollection { - private static final String TAG = "davdroid.LocalCollection"; - - protected Account account; - protected ContentProviderClient providerClient; - protected ArrayList pendingOperations = new ArrayList(); - - - // database fields - - /** base Uri of the collection's entries (for instance, Events.CONTENT_URI); - * apply syncAdapterURI() before returning a value */ - abstract protected Uri entriesURI(); - - /** column name of the type of the account the entry belongs to */ - abstract protected String entryColumnAccountType(); - /** column name of the name of the account the entry belongs to */ - abstract protected String entryColumnAccountName(); - - /** column name of the collection ID the entry belongs to */ - abstract protected String entryColumnParentID(); - /** column name of an entry's ID */ - abstract protected String entryColumnID(); - /** column name of an entry's file name on the WebDAV server */ - abstract protected String entryColumnRemoteName(); - /** column name of an entry's last ETag on the WebDAV server; null if entry hasn't been uploaded yet */ - abstract protected String entryColumnETag(); - - /** column name of an entry's "dirty" flag (managed by content provider) */ - abstract protected String entryColumnDirty(); - /** column name of an entry's "deleted" flag (managed by content provider) */ - abstract protected String entryColumnDeleted(); - - /** column name of an entry's UID */ - abstract protected String entryColumnUID(); - - - LocalCollection(Account account, ContentProviderClient providerClient) { - this.account = account; - this.providerClient = providerClient; - } - - - // collection operations - - /** gets the ID if the collection (for instance, ID of the Android calendar) */ - abstract public long getId(); - /** gets the CTag of the collection */ - abstract public String getCTag() throws LocalStorageException; - /** sets the CTag of the collection */ - abstract public void setCTag(String cTag) throws LocalStorageException; - - - // content provider (= database) querying - - /** - * Finds new resources (resources which haven't been uploaded yet). - * New resources are 1) dirty, and 2) don't have an ETag yet. - * - * @return IDs of new resources - * @throws LocalStorageException when the content provider couldn't be queried - */ - public long[] findNew() throws LocalStorageException { - String where = entryColumnDirty() + "=1 AND " + entryColumnETag() + " 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.initialize(); - // 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); - } - } - - /** - * Finds updated resources (resources which have already been uploaded, but have changed locally). - * Updated resources are 1) dirty, and 2) already have an ETag. - * - * @return IDs of updated resources - * @throws LocalStorageException when the content provider couldn't be queried - */ - public long[] findUpdated() throws LocalStorageException { - String where = entryColumnDirty() + "=1 AND " + entryColumnETag() + " IS NOT NULL"; - if (entryColumnParentID() != null) - where += " AND " + entryColumnParentID() + "=" + String.valueOf(getId()); - try { - @Cleanup Cursor cursor = providerClient.query(entriesURI(), - new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() }, - where, null, null); - if (cursor == null) - throw new LocalStorageException("Couldn't query updated records"); - - long[] updated = new long[cursor.getCount()]; - for (int idx = 0; cursor.moveToNext(); idx++) - updated[idx] = cursor.getLong(0); - return updated; - } catch(RemoteException ex) { - throw new LocalStorageException(ex); - } - } - - /** - * Finds deleted resources (resources which have been marked for deletion). - * Deleted resources have the "deleted" flag set. - * - * @return IDs of deleted resources - * @throws LocalStorageException when the content provider couldn't be queried - */ - public long[] findDeleted() throws LocalStorageException { - String where = entryColumnDeleted() + "=1"; - if (entryColumnParentID() != null) - where += " AND " + entryColumnParentID() + "=" + String.valueOf(getId()); - try { - @Cleanup Cursor cursor = providerClient.query(entriesURI(), - new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() }, - where, null, null); - if (cursor == null) - throw new LocalStorageException("Couldn't query dirty records"); - - long deleted[] = new long[cursor.getCount()]; - for (int idx = 0; cursor.moveToNext(); idx++) - deleted[idx] = cursor.getLong(0); - return deleted; - } catch(RemoteException ex) { - throw new LocalStorageException(ex); - } - } - - /** - * Finds a specific resource by ID. - * @param localID ID of the resource - * @param populate true: populates all data fields (for instance, contact or event details); - * false: only remote file name and ETag are populated - * @return resource with either ID/remote file/name/ETag or all fields populated - * @throws RecordNotFoundException when the resource couldn't be found - * @throws LocalStorageException when the content provider couldn't be queried - */ - public T findById(long localID, boolean populate) throws LocalStorageException { - try { - @Cleanup Cursor cursor = providerClient.query(ContentUris.withAppendedId(entriesURI(), localID), - new String[] { entryColumnRemoteName(), entryColumnETag() }, null, null, null); - if (cursor != null && cursor.moveToNext()) { - T resource = newResource(localID, cursor.getString(0), cursor.getString(1)); - if (populate) - populate(resource); - return resource; - } else - throw new RecordNotFoundException(); - } catch(RemoteException ex) { - throw new LocalStorageException(ex); - } - } - - /** - * Finds a specific resource by remote file name. - * @param localID remote file name of the resource - * @param populate true: populates all data fields (for instance, contact or event details); - * false: only remote file name and ETag are populated - * @return resource with either ID/remote file/name/ETag or all fields populated - * @throws RecordNotFoundException when the resource couldn't be found - * @throws LocalStorageException when the content provider couldn't be queried - */ - public T findByRemoteName(String remoteName, boolean populate) throws LocalStorageException { - try { - @Cleanup Cursor cursor = providerClient.query(entriesURI(), - new String[] { entryColumnID(), entryColumnRemoteName(), entryColumnETag() }, - entryColumnRemoteName() + "=?", new String[] { remoteName }, null); - if (cursor != null && cursor.moveToNext()) { - T resource = newResource(cursor.getLong(0), cursor.getString(1), cursor.getString(2)); - if (populate) - populate(resource); - return resource; - } else - throw new RecordNotFoundException(); - } catch(RemoteException ex) { - throw new LocalStorageException(ex); - } - } - - /** populates all data fields from the content provider */ - public abstract void populate(Resource record) throws LocalStorageException; - - - // create/update/delete - - /** - * Creates a new resource object in memory. No content provider operations involved. - * @param localID the ID of the resource - * @param resourceName the (remote) file name of the resource - * @param ETag of the resource - * @return the new resource object */ - abstract public T newResource(long localID, String resourceName, String eTag); - - /** Enqueues adding the resource (including all data) to the local collection. Requires commit(). */ - public void add(Resource resource) { - int idx = pendingOperations.size(); - pendingOperations.add( - buildEntry(ContentProviderOperation.newInsert(entriesURI()), resource) - .withYieldAllowed(true) - .build()); - - addDataRows(resource, -1, idx); - } - - /** Enqueues updating an existing resource in the local collection. The resource will be found by - * the remote file name and all data will be updated. Requires commit(). */ - public void updateByRemoteName(Resource remoteResource) throws LocalStorageException { - T localResource = findByRemoteName(remoteResource.getName(), false); - pendingOperations.add( - buildEntry(ContentProviderOperation.newUpdate(ContentUris.withAppendedId(entriesURI(), localResource.getLocalID())), remoteResource) - .withValue(entryColumnETag(), remoteResource.getETag()) - .withYieldAllowed(true) - .build()); - - removeDataRows(localResource); - addDataRows(remoteResource, localResource.getLocalID(), -1); - } - - /** Enqueues deleting a resource from the local collection. Requires commit(). */ - public void delete(Resource resource) { - pendingOperations.add(ContentProviderOperation - .newDelete(ContentUris.withAppendedId(entriesURI(), resource.getLocalID())) - .withYieldAllowed(true) - .build()); - } - - /** - * Enqueues deleting all resources except the give ones from the local collection. Requires commit(). - * @param remoteResources resources with these remote file names will be kept - */ - public abstract void deleteAllExceptRemoteNames(Resource[] remoteResources); - - /** Updates the locally-known ETag of a resource. */ - public void updateETag(Resource res, String eTag) throws LocalStorageException { - Log.d(TAG, "Setting ETag of local resource " + res + " to " + eTag); - - ContentValues values = new ContentValues(1); - values.put(entryColumnETag(), eTag); - try { - providerClient.update(ContentUris.withAppendedId(entriesURI(), res.getLocalID()), values, null, new String[] {}); - } catch (RemoteException e) { - throw new LocalStorageException(e); - } - } - - /** Enqueues removing the dirty flag from a locally-stored resource. Requires commit(). */ - public void clearDirty(Resource resource) { - pendingOperations.add(ContentProviderOperation - .newUpdate(ContentUris.withAppendedId(entriesURI(), resource.getLocalID())) - .withValue(entryColumnDirty(), 0) - .build()); - } - - /** Commits enqueued operations to the content provider (for batch operations). */ - public void commit() throws LocalStorageException { - if (!pendingOperations.isEmpty()) - try { - Log.d(TAG, "Committing " + pendingOperations.size() + " operations"); - providerClient.applyBatch(pendingOperations); - pendingOperations.clear(); - } catch (RemoteException ex) { - throw new LocalStorageException(ex); - } catch(OperationApplicationException ex) { - throw new LocalStorageException(ex); - } - } - - - // helpers - - protected void queueOperation(Builder builder) { - if (builder != null) - pendingOperations.add(builder.build()); - } - - /** Appends account type, name and CALLER_IS_SYNCADAPTER to an Uri. */ - protected Uri syncAdapterURI(Uri baseURI) { - return baseURI.buildUpon() - .appendQueryParameter(entryColumnAccountType(), account.type) - .appendQueryParameter(entryColumnAccountName(), account.name) - .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true") - .build(); - } - - protected Builder newDataInsertBuilder(Uri dataUri, String refFieldName, long raw_ref_id, Integer backrefIdx) { - Builder builder = ContentProviderOperation.newInsert(syncAdapterURI(dataUri)); - if (backrefIdx != -1) - return builder.withValueBackReference(refFieldName, backrefIdx); - else - return builder.withValue(refFieldName, raw_ref_id); - } - - - // content builders - - /** - * Builds the main entry (for instance, a ContactsContract.RawContacts row) from a resource. - * The entry is built for insertion to the location identified by entriesURI(). - * - * @param builder Builder to be extended by all resource data that can be stored without extra data rows. - */ - protected abstract Builder buildEntry(Builder builder, Resource resource); - - /** Enqueues adding extra data rows of the resource to the local collection. */ - protected abstract void addDataRows(Resource resource, long localID, int backrefIdx); - - /** Enqueues removing all extra data rows of the resource from the local collection. */ - protected abstract void removeDataRows(Resource resource); -} diff --git a/src/at/bitfire/davdroid/resource/LocalStorageException.java b/src/at/bitfire/davdroid/resource/LocalStorageException.java deleted file mode 100644 index d33ad46a..00000000 --- a/src/at/bitfire/davdroid/resource/LocalStorageException.java +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -public class LocalStorageException extends Exception { - private static final long serialVersionUID = -7787658815291629529L; - - private static final String detailMessage = "Couldn't access local content provider"; - - - public LocalStorageException(String detailMessage, Throwable throwable) { - super(detailMessage, throwable); - } - - public LocalStorageException(String detailMessage) { - super(detailMessage); - } - - public LocalStorageException(Throwable throwable) { - super(detailMessage, throwable); - } - - public LocalStorageException() { - super(detailMessage); - } -} diff --git a/src/at/bitfire/davdroid/resource/RecordNotFoundException.java b/src/at/bitfire/davdroid/resource/RecordNotFoundException.java deleted file mode 100644 index 98daa77c..00000000 --- a/src/at/bitfire/davdroid/resource/RecordNotFoundException.java +++ /dev/null @@ -1,28 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -/** - * Thrown when a local record (for instance, Contact with ID 12345) should be read - * but could not be found. - */ -public class RecordNotFoundException extends LocalStorageException { - private static final long serialVersionUID = 4961024282198632578L; - - private static final String detailMessage = "Record not found in local content provider"; - - - RecordNotFoundException(Throwable ex) { - super(detailMessage, ex); - } - - RecordNotFoundException() { - super(detailMessage); - } - -} diff --git a/src/at/bitfire/davdroid/resource/RemoteCollection.java b/src/at/bitfire/davdroid/resource/RemoteCollection.java deleted file mode 100644 index d287e823..00000000 --- a/src/at/bitfire/davdroid/resource/RemoteCollection.java +++ /dev/null @@ -1,181 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.LinkedList; -import java.util.List; - -import lombok.Cleanup; -import lombok.Getter; -import net.fortuna.ical4j.model.ValidationException; -import android.util.Log; -import at.bitfire.davdroid.webdav.DavException; -import at.bitfire.davdroid.webdav.DavMultiget; -import at.bitfire.davdroid.webdav.DavNoContentException; -import at.bitfire.davdroid.webdav.HttpException; -import at.bitfire.davdroid.webdav.HttpPropfind; -import at.bitfire.davdroid.webdav.WebDavResource; -import at.bitfire.davdroid.webdav.WebDavResource.PutMode; -import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient; -import ezvcard.io.text.VCardParseException; - -/** - * Represents a remotely stored synchronizable collection (collection as in - * WebDAV terminology). - * - * @param Subtype of Resource that can be stored in the collection - */ -public abstract class RemoteCollection { - private static final String TAG = "davdroid.RemoteCollection"; - - CloseableHttpClient httpClient; - @Getter WebDavResource collection; - - abstract protected String memberContentType(); - abstract protected DavMultiget.Type multiGetType(); - abstract protected T newResourceSkeleton(String name, String ETag); - - public RemoteCollection(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws MalformedURLException { - this.httpClient = httpClient; - - collection = new WebDavResource(httpClient, new URL(baseURL), user, password, preemptiveAuth); - } - - - /* collection operations */ - - public String getCTag() throws URISyntaxException, IOException, HttpException { - try { - if (collection.getCTag() == null && collection.getMembers() == null) // not already fetched - collection.propfind(HttpPropfind.Mode.COLLECTION_CTAG); - } catch (DavException e) { - return null; - } - return collection.getCTag(); - } - - public Resource[] getMemberETags() throws URISyntaxException, IOException, DavException, HttpException { - collection.propfind(HttpPropfind.Mode.MEMBERS_ETAG); - - List resources = new LinkedList(); - if (collection.getMembers() != null) { - for (WebDavResource member : collection.getMembers()) - resources.add(newResourceSkeleton(member.getName(), member.getETag())); - } - return resources.toArray(new Resource[0]); - } - - @SuppressWarnings("unchecked") - public Resource[] multiGet(Resource[] resources) throws URISyntaxException, IOException, DavException, HttpException { - try { - if (resources.length == 1) - return (T[]) new Resource[] { get(resources[0]) }; - - Log.i(TAG, "Multi-getting " + resources.length + " remote resource(s)"); - - LinkedList names = new LinkedList(); - for (Resource resource : resources) - names.add(resource.getName()); - - LinkedList foundResources = new LinkedList(); - collection.multiGet(multiGetType(), names.toArray(new String[0])); - if (collection.getMembers() == null) - throw new DavNoContentException(); - - for (WebDavResource member : collection.getMembers()) { - T resource = newResourceSkeleton(member.getName(), member.getETag()); - try { - if (member.getContent() != null) { - @Cleanup InputStream is = new ByteArrayInputStream(member.getContent()); - resource.parseEntity(is); - foundResources.add(resource); - } else - Log.e(TAG, "Ignoring entity without content"); - } catch (InvalidResourceException e) { - Log.e(TAG, "Ignoring unparseable entity in multi-response", e); - } - } - - return foundResources.toArray(new Resource[0]); - } catch (InvalidResourceException e) { - Log.e(TAG, "Couldn't parse entity from GET", e); - } - - return new Resource[0]; - } - - - /* internal member operations */ - - 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"); - } - - byte[] data = member.getContent(); - if (data == null) - throw new DavNoContentException(); - - @Cleanup InputStream is = new ByteArrayInputStream(data); - try { - resource.parseEntity(is); - } catch(VCardParseException e) { - throw new InvalidResourceException(e); - } - return 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()); - - @Cleanup ByteArrayOutputStream os = res.toEntity(); - String eTag = member.put(os.toByteArray(), PutMode.ADD_DONT_OVERWRITE); - - // after a successful upload, the collection has implicitely changed, too - collection.invalidateCTag(); - - return eTag; - } - - public void delete(Resource res) throws URISyntaxException, IOException, HttpException { - WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag()); - member.delete(); - - collection.invalidateCTag(); - } - - // 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()); - - @Cleanup ByteArrayOutputStream os = res.toEntity(); - String eTag = member.put(os.toByteArray(), PutMode.UPDATE_DONT_OVERWRITE); - - // after a successful upload, the collection has implicitely changed, too - collection.invalidateCTag(); - - return eTag; - } -} diff --git a/src/at/bitfire/davdroid/resource/Resource.java b/src/at/bitfire/davdroid/resource/Resource.java deleted file mode 100644 index bda3e2e5..00000000 --- a/src/at/bitfire/davdroid/resource/Resource.java +++ /dev/null @@ -1,46 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -/** - * Represents a resource that can be contained in a LocalCollection or RemoteCollection - * for synchronization by WebDAV. - */ -@ToString -public abstract class Resource { - @Getter @Setter protected String name, ETag; - @Getter @Setter protected String uid; - @Getter protected long localID; - - - public Resource(String name, String ETag) { - this.name = name; - this.ETag = ETag; - } - - public Resource(long localID, String name, String ETag) { - this(name, ETag); - this.localID = localID; - } - - /** initializes UID and remote file name (required for first upload) */ - public abstract void initialize(); - - /** fills the resource data from an input stream (for instance, .vcf file for Contact) */ - public abstract void parseEntity(InputStream entity) throws IOException, InvalidResourceException; - /** writes the resource data to an output stream (for instance, .vcf file for Contact) */ - public abstract ByteArrayOutputStream toEntity() throws IOException; -} diff --git a/src/at/bitfire/davdroid/resource/ServerInfo.java b/src/at/bitfire/davdroid/resource/ServerInfo.java deleted file mode 100644 index 14f5b6f7..00000000 --- a/src/at/bitfire/davdroid/resource/ServerInfo.java +++ /dev/null @@ -1,68 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource; - -import java.io.Serializable; -import java.net.URI; -import java.util.LinkedList; -import java.util.List; - -import ezvcard.VCardVersion; -import lombok.Data; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor(suppressConstructorProperties=true) -@Data -public class ServerInfo implements Serializable { - private static final long serialVersionUID = 6744847358282980437L; - - enum Scheme { - HTTP, HTTPS, MAILTO - } - - final private URI baseURI; - final private String userName, password; - final boolean authPreemptive; - - private String errorMessage; - - private boolean calDAV = false, cardDAV = false; - private List - addressBooks = new LinkedList(), - calendars = new LinkedList(); - - - public boolean hasEnabledCalendars() { - for (ResourceInfo calendar : calendars) - if (calendar.enabled) - return true; - return false; - } - - - @RequiredArgsConstructor(suppressConstructorProperties=true) - @Data - public static class ResourceInfo implements Serializable { - private static final long serialVersionUID = -5516934508229552112L; - - public enum Type { - ADDRESS_BOOK, - CALENDAR - } - - boolean enabled = false; - - final Type type; - final boolean readOnly; - final String URL, title, description, color; - - VCardVersion vCardVersion; - - String timezone; - } -} diff --git a/src/at/bitfire/davdroid/syncadapter/AccountAuthenticatorService.java b/src/at/bitfire/davdroid/syncadapter/AccountAuthenticatorService.java deleted file mode 100644 index f78cc4f6..00000000 --- a/src/at/bitfire/davdroid/syncadapter/AccountAuthenticatorService.java +++ /dev/null @@ -1,86 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import android.accounts.AbstractAccountAuthenticator; -import android.accounts.Account; -import android.accounts.AccountAuthenticatorResponse; -import android.accounts.AccountManager; -import android.accounts.NetworkErrorException; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.IBinder; - -public class AccountAuthenticatorService extends Service { - private static AccountAuthenticator accountAuthenticator; - - private AccountAuthenticator getAuthenticator() { - if (accountAuthenticator != null) - return accountAuthenticator; - return accountAuthenticator = new AccountAuthenticator(this); - } - - @Override - public IBinder onBind(Intent intent) { - if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT)) - return getAuthenticator().getIBinder(); - return null; - } - - - private static class AccountAuthenticator extends AbstractAccountAuthenticator { - Context context; - - public AccountAuthenticator(Context context) { - super(context); - this.context = context; - } - - @Override - public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, - String[] requiredFeatures, Bundle options) throws NetworkErrorException { - Intent intent = new Intent(context, AddAccountActivity.class); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); - Bundle bundle = new Bundle(); - bundle.putParcelable(AccountManager.KEY_INTENT, intent); - return bundle; - } - - @Override - public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { - return null; - } - - @Override - public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { - return null; - } - - @Override - public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { - return null; - } - - @Override - public String getAuthTokenLabel(String authTokenType) { - return null; - } - - @Override - public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { - return null; - } - - @Override - public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { - return null; - } - } -} diff --git a/src/at/bitfire/davdroid/syncadapter/AccountDetailsFragment.java b/src/at/bitfire/davdroid/syncadapter/AccountDetailsFragment.java deleted file mode 100644 index 37ab4734..00000000 --- a/src/at/bitfire/davdroid/syncadapter/AccountDetailsFragment.java +++ /dev/null @@ -1,147 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.Fragment; -import android.content.ContentResolver; -import android.os.Bundle; -import android.provider.CalendarContract; -import android.provider.ContactsContract; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; -import at.bitfire.davdroid.Constants; -import at.bitfire.davdroid.R; -import at.bitfire.davdroid.resource.LocalCalendar; -import at.bitfire.davdroid.resource.LocalStorageException; -import at.bitfire.davdroid.resource.ServerInfo; - -public class AccountDetailsFragment extends Fragment implements TextWatcher { - public static final String KEY_SERVER_INFO = "server_info"; - - ServerInfo serverInfo; - - EditText editAccountName; - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.account_details, container, false); - - serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO); - - editAccountName = (EditText)v.findViewById(R.id.account_name); - editAccountName.addTextChangedListener(this); - editAccountName.setText(serverInfo.getUserName()); - - TextView textAccountNameInfo = (TextView)v.findViewById(R.id.account_name_info); - if (!serverInfo.hasEnabledCalendars()) - textAccountNameInfo.setVisibility(View.GONE); - - setHasOptionsMenu(true); - return v; - } - - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.account_details, menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.add_account: - addAccount(); - break; - default: - return false; - } - return true; - } - - - // actions - - void addAccount() { - ServerInfo serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO); - String accountName = editAccountName.getText().toString(); - - AccountManager accountManager = AccountManager.get(getActivity()); - Account account = new Account(accountName, Constants.ACCOUNT_TYPE); - Bundle userData = AccountSettings.createBundle(serverInfo); - - boolean syncContacts = false; - for (ServerInfo.ResourceInfo addressBook : serverInfo.getAddressBooks()) - if (addressBook.isEnabled()) { - ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1); - syncContacts = true; - continue; - } - if (syncContacts) { - ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1); - ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true); - } else - ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0); - - if (accountManager.addAccountExplicitly(account, serverInfo.getPassword(), userData)) { - // account created, now create calendars - boolean syncCalendars = false; - for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars()) - if (calendar.isEnabled()) - try { - LocalCalendar.create(account, getActivity().getContentResolver(), calendar); - syncCalendars = true; - } catch (LocalStorageException e) { - Toast.makeText(getActivity(), "Couldn't create calendar(s): " + e.getMessage(), Toast.LENGTH_LONG).show(); - } - if (syncCalendars) { - ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1); - ContentResolver.setSyncAutomatically(account, CalendarContract.AUTHORITY, true); - } else - ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0); - - getActivity().finish(); - } else - Toast.makeText(getActivity(), "Couldn't create account (account with this name already existing?)", Toast.LENGTH_LONG).show(); - } - - - // input validation - - @Override - public void onPrepareOptionsMenu(Menu menu) { - boolean ok = false; - ok = editAccountName.getText().length() > 0; - MenuItem item = menu.findItem(R.id.add_account); - item.setEnabled(ok); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - getActivity().invalidateOptionsMenu(); - } - - @Override - public void afterTextChanged(Editable s) { - } -} diff --git a/src/at/bitfire/davdroid/syncadapter/AccountSettings.java b/src/at/bitfire/davdroid/syncadapter/AccountSettings.java deleted file mode 100644 index a58fe407..00000000 --- a/src/at/bitfire/davdroid/syncadapter/AccountSettings.java +++ /dev/null @@ -1,183 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import java.net.URI; -import java.net.URISyntaxException; - -import lombok.Cleanup; -import android.accounts.Account; -import android.accounts.AccountManager; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.provider.CalendarContract; -import android.provider.CalendarContract.Calendars; -import android.util.Log; -import at.bitfire.davdroid.resource.ServerInfo; -import ezvcard.VCardVersion; - -public class AccountSettings { - private final static String TAG = "davdroid.AccountSettings"; - - private final static int CURRENT_VERSION = 1; - private final static String - KEY_SETTINGS_VERSION = "version", - - KEY_USERNAME = "user_name", - KEY_AUTH_PREEMPTIVE = "auth_preemptive", - - KEY_ADDRESSBOOK_URL = "addressbook_url", - KEY_ADDRESSBOOK_CTAG = "addressbook_ctag", - KEY_ADDRESSBOOK_VCARD_VERSION = "addressbook_vcard_version"; - - Context context; - AccountManager accountManager; - Account account; - - - public AccountSettings(Context context, Account account) { - this.context = context; - this.account = account; - - accountManager = AccountManager.get(context); - - synchronized(AccountSettings.class) { - int version = 0; - try { - version = Integer.parseInt(accountManager.getUserData(account, KEY_SETTINGS_VERSION)); - } catch(NumberFormatException e) { - } - if (version < CURRENT_VERSION) - update(version); - } - } - - - public static Bundle createBundle(ServerInfo serverInfo) { - Bundle bundle = new Bundle(); - bundle.putString(KEY_SETTINGS_VERSION, String.valueOf(CURRENT_VERSION)); - bundle.putString(KEY_USERNAME, serverInfo.getUserName()); - bundle.putString(KEY_AUTH_PREEMPTIVE, Boolean.toString(serverInfo.isAuthPreemptive())); - for (ServerInfo.ResourceInfo addressBook : serverInfo.getAddressBooks()) - if (addressBook.isEnabled()) { - bundle.putString(KEY_ADDRESSBOOK_URL, addressBook.getURL()); - bundle.putString(KEY_ADDRESSBOOK_VCARD_VERSION, addressBook.getVCardVersion().getVersion()); - continue; - } - return bundle; - } - - - // general settings - - public String getUserName() { - return accountManager.getUserData(account, KEY_USERNAME); - } - - public String getPassword() { - return accountManager.getPassword(account); - } - - public boolean getPreemptiveAuth() { - return Boolean.parseBoolean(accountManager.getUserData(account, KEY_AUTH_PREEMPTIVE)); - } - - - // address book (CardDAV) settings - - public String getAddressBookURL() { - return accountManager.getUserData(account, KEY_ADDRESSBOOK_URL); - } - - public String getAddressBookCTag() { - return accountManager.getUserData(account, KEY_ADDRESSBOOK_CTAG); - } - - public void setAddressBookCTag(String cTag) { - accountManager.setUserData(account, KEY_ADDRESSBOOK_CTAG, cTag); - } - - public VCardVersion getAddressBookVCardVersion() { - VCardVersion version = VCardVersion.V3_0; - String versionStr = accountManager.getUserData(account, KEY_ADDRESSBOOK_VCARD_VERSION); - if (versionStr != null) - version = VCardVersion.valueOfByStr(versionStr); - return version; - } - - - // update from previous account settings - - private void update(int fromVersion) { - Log.i(TAG, "Account settings must be updated from v" + fromVersion + " to v" + CURRENT_VERSION); - for (int toVersion = CURRENT_VERSION; toVersion > fromVersion; toVersion--) - update(fromVersion, toVersion); - } - - private void update(int fromVersion, int toVersion) { - Log.i(TAG, "Updating account settings from v" + fromVersion + " to " + toVersion); - try { - if (fromVersion == 0 && toVersion == 1) - update_0_1(); - else - Log.wtf(TAG, "Don't know how to update settings from v" + fromVersion + " to v" + toVersion); - } catch(Exception e) { - Log.e(TAG, "Couldn't update account settings (DAVdroid will probably crash)!", e); - } - } - - private void update_0_1() throws URISyntaxException { - String v0_principalURL = accountManager.getUserData(account, "principal_url"), - v0_addressBookPath = accountManager.getUserData(account, "addressbook_path"); - Log.d(TAG, "Old principal URL = " + v0_principalURL); - Log.d(TAG, "Old address book path = " + v0_addressBookPath); - - URI principalURI = new URI(v0_principalURL); - - // update address book - if (v0_addressBookPath != null) { - String addressBookURL = principalURI.resolve(v0_addressBookPath).toASCIIString(); - Log.d(TAG, "New address book URL = " + addressBookURL); - accountManager.setUserData(account, "addressbook_url", addressBookURL); - } - - // update calendars - ContentResolver resolver = context.getContentResolver(); - Uri calendars = Calendars.CONTENT_URI.buildUpon() - .appendQueryParameter(Calendars.ACCOUNT_NAME, account.name) - .appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type) - .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true").build(); - @Cleanup Cursor cursor = resolver.query(calendars, new String[] { Calendars._ID, Calendars.NAME }, null, null, null); - while (cursor != null && cursor.moveToNext()) { - int id = cursor.getInt(0); - String v0_path = cursor.getString(1), - v1_url = principalURI.resolve(v0_path).toASCIIString(); - Log.d(TAG, "Updating calendar #" + id + " name: " + v0_path + " -> " + v1_url); - Uri calendar = ContentUris.appendId(Calendars.CONTENT_URI.buildUpon() - .appendQueryParameter(Calendars.ACCOUNT_NAME, account.name) - .appendQueryParameter(Calendars.ACCOUNT_TYPE, account.type) - .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true"), id).build(); - ContentValues newValues = new ContentValues(1); - newValues.put(Calendars.NAME, v1_url); - if (resolver.update(calendar, newValues, null, null) != 1) - Log.e(TAG, "Number of modified calendars != 1"); - } - - Log.d(TAG, "Cleaning old principal URL and address book path"); - accountManager.setUserData(account, "principal_url", null); - accountManager.setUserData(account, "addressbook_path", null); - - Log.d(TAG, "Updated settings successfully!"); - accountManager.setUserData(account, KEY_SETTINGS_VERSION, "1"); - } -} diff --git a/src/at/bitfire/davdroid/syncadapter/AddAccountActivity.java b/src/at/bitfire/davdroid/syncadapter/AddAccountActivity.java deleted file mode 100644 index f65f42f9..00000000 --- a/src/at/bitfire/davdroid/syncadapter/AddAccountActivity.java +++ /dev/null @@ -1,46 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import android.app.Activity; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import at.bitfire.davdroid.Constants; -import at.bitfire.davdroid.R; - -public class AddAccountActivity extends Activity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.add_account); - - if (savedInstanceState == null) { // first call - getFragmentManager().beginTransaction() - .add(R.id.fragment_container, new LoginTypeFragment(), "login_type") - .commit(); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.add_account, menu); - return true; - } - - public void showHelp(MenuItem item) { - startActivityForResult(new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.WEB_URL_HELP)), 0); - } - -} diff --git a/src/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java b/src/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java deleted file mode 100644 index f00678cd..00000000 --- a/src/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java +++ /dev/null @@ -1,81 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import java.net.MalformedURLException; -import java.util.HashMap; -import java.util.Map; - -import android.accounts.Account; -import android.app.Service; -import android.content.ContentProviderClient; -import android.content.Context; -import android.content.Intent; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; -import at.bitfire.davdroid.resource.CalDavCalendar; -import at.bitfire.davdroid.resource.LocalCalendar; -import at.bitfire.davdroid.resource.LocalCollection; -import at.bitfire.davdroid.resource.RemoteCollection; - -public class CalendarsSyncAdapterService extends Service { - private static SyncAdapter syncAdapter; - - - @Override - public void onCreate() { - if (syncAdapter == null) - syncAdapter = new SyncAdapter(getApplicationContext()); - } - - @Override - public void onDestroy() { - syncAdapter.close(); - syncAdapter = null; - } - - @Override - public IBinder onBind(Intent intent) { - return syncAdapter.getSyncAdapterBinder(); - } - - - private static class SyncAdapter extends DavSyncAdapter { - private final static String TAG = "davdroid.CalendarsSyncAdapter"; - - - private SyncAdapter(Context context) { - super(context); - } - - @Override - protected Map, RemoteCollection> getSyncPairs(Account account, ContentProviderClient provider) { - AccountSettings settings = new AccountSettings(getContext(), account); - String userName = settings.getUserName(), - password = settings.getPassword(); - boolean preemptive = settings.getPreemptiveAuth(); - - try { - Map, RemoteCollection> map = new HashMap, RemoteCollection>(); - - for (LocalCalendar calendar : LocalCalendar.findAll(account, provider)) { - RemoteCollection dav = new CalDavCalendar(httpClient, calendar.getUrl(), userName, password, preemptive); - map.put(calendar, dav); - } - return map; - } catch (RemoteException ex) { - Log.e(TAG, "Couldn't find local calendars", ex); - } catch (MalformedURLException ex) { - Log.e(TAG, "Couldn't build calendar URI", ex); - } - - return null; - } - } -} diff --git a/src/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java b/src/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java deleted file mode 100644 index 88328700..00000000 --- a/src/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java +++ /dev/null @@ -1,82 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import java.net.MalformedURLException; -import java.util.HashMap; -import java.util.Map; - -import android.accounts.Account; -import android.app.Service; -import android.content.ContentProviderClient; -import android.content.Context; -import android.content.Intent; -import android.os.IBinder; -import android.util.Log; -import at.bitfire.davdroid.resource.CardDavAddressBook; -import at.bitfire.davdroid.resource.LocalAddressBook; -import at.bitfire.davdroid.resource.LocalCollection; -import at.bitfire.davdroid.resource.RemoteCollection; - -public class ContactsSyncAdapterService extends Service { - private static ContactsSyncAdapter syncAdapter; - - - @Override - public void onCreate() { - if (syncAdapter == null) - syncAdapter = new ContactsSyncAdapter(getApplicationContext()); - } - - @Override - public void onDestroy() { - syncAdapter.close(); - syncAdapter = null; - } - - @Override - public IBinder onBind(Intent intent) { - return syncAdapter.getSyncAdapterBinder(); - } - - - private static class ContactsSyncAdapter extends DavSyncAdapter { - private final static String TAG = "davdroid.ContactsSyncAdapter"; - - - private ContactsSyncAdapter(Context context) { - super(context); - } - - @Override - protected Map, RemoteCollection> getSyncPairs(Account account, ContentProviderClient provider) { - AccountSettings settings = new AccountSettings(getContext(), account); - String userName = settings.getUserName(), - password = settings.getPassword(); - boolean preemptive = settings.getPreemptiveAuth(); - - String addressBookURL = settings.getAddressBookURL(); - if (addressBookURL == null) - return null; - - try { - LocalCollection database = new LocalAddressBook(account, provider, settings); - RemoteCollection dav = new CardDavAddressBook(httpClient, addressBookURL, userName, password, preemptive); - - Map, RemoteCollection> map = new HashMap, RemoteCollection>(); - map.put(database, dav); - - return map; - } catch (MalformedURLException ex) { - Log.e(TAG, "Couldn't build address book URI", ex); - } - - return null; - } - } -} diff --git a/src/at/bitfire/davdroid/syncadapter/DavSyncAdapter.java b/src/at/bitfire/davdroid/syncadapter/DavSyncAdapter.java deleted file mode 100644 index 71189f4e..00000000 --- a/src/at/bitfire/davdroid/syncadapter/DavSyncAdapter.java +++ /dev/null @@ -1,166 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import java.io.Closeable; -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.Map; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import lombok.Getter; - -import org.apache.http.HttpStatus; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.content.AbstractThreadedSyncAdapter; -import android.content.ContentProviderClient; -import android.content.ContentResolver; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SyncResult; -import android.os.AsyncTask; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.provider.Settings; -import android.util.Log; -import at.bitfire.davdroid.Constants; -import at.bitfire.davdroid.resource.LocalCollection; -import at.bitfire.davdroid.resource.LocalStorageException; -import at.bitfire.davdroid.resource.RemoteCollection; -import at.bitfire.davdroid.webdav.DavException; -import at.bitfire.davdroid.webdav.DavHttpClient; -import at.bitfire.davdroid.webdav.HttpException; -import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient; - -public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter implements Closeable { - private final static String TAG = "davdroid.DavSyncAdapter"; - - @Getter private static String androidID; - - protected AccountManager accountManager; - - /* We use one static httpClient for - * - all sync adapters (CalendarsSyncAdapter, ContactsSyncAdapter) - * - and all threads (= accounts) of each sync adapter - * so that HttpClient's threaded pool management can do its best. - */ - protected static CloseableHttpClient httpClient; - - /* One static read/write lock pair for the static httpClient: - * Use the READ lock when httpClient will only be called (to prevent it from being unset while being used). - * Use the WRITE lock when httpClient will be modified (set/unset). */ - private final static ReentrantReadWriteLock httpClientLock = new ReentrantReadWriteLock(); - - - public DavSyncAdapter(Context context) { - super(context, true); - - synchronized(this) { - if (androidID == null) - androidID = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); - } - - accountManager = AccountManager.get(context); - } - - @Override - public void close() { - Log.d(TAG, "Closing httpClient"); - - // may be called from a GUI thread, so we need an AsyncTask - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - try { - httpClientLock.writeLock().lock(); - if (httpClient != null) { - httpClient.close(); - httpClient = null; - } - httpClientLock.writeLock().unlock(); - } catch (IOException e) { - Log.w(TAG, "Couldn't close HTTP client", e); - } - return null; - } - }.execute(); - } - - protected abstract Map, RemoteCollection> getSyncPairs(Account account, ContentProviderClient provider); - - - @Override - public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { - Log.i(TAG, "Performing sync for authority " + authority); - - // set class loader for iCal4j ResourceLoader - Thread.currentThread().setContextClassLoader(getContext().getClassLoader()); - - // create httpClient, if necessary - httpClientLock.writeLock().lock(); - if (httpClient == null) { - Log.d(TAG, "Creating new DavHttpClient"); - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getContext()); - httpClient = DavHttpClient.create( - settings.getBoolean(Constants.SETTING_DISABLE_COMPRESSION, false), - settings.getBoolean(Constants.SETTING_NETWORK_LOGGING, false) - ); - } - - // prevent httpClient shutdown until we're ready by holding a read lock - // acquiring read lock before releasing write lock will downgrade the write lock to a read lock - httpClientLock.readLock().lock(); - httpClientLock.writeLock().unlock(); - - // TODO use VCard 4.0 if possible - AccountSettings accountSettings = new AccountSettings(getContext(), account); - Log.d(TAG, "Server supports VCard version " + accountSettings.getAddressBookVCardVersion()); - - try { - // get local <-> remote collection pairs - Map, RemoteCollection> syncCollections = getSyncPairs(account, provider); - if (syncCollections == null) - Log.i(TAG, "Nothing to synchronize"); - else - try { - for (Map.Entry, RemoteCollection> entry : syncCollections.entrySet()) - new SyncManager(entry.getKey(), entry.getValue()).synchronize(extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL), syncResult); - } catch (DavException ex) { - syncResult.stats.numParseExceptions++; - Log.e(TAG, "Invalid DAV response", ex); - } catch (HttpException ex) { - if (ex.getCode() == HttpStatus.SC_UNAUTHORIZED) { - Log.e(TAG, "HTTP Unauthorized " + ex.getCode(), ex); - syncResult.stats.numAuthExceptions++; - } else if (ex.isClientError()) { - Log.e(TAG, "Hard HTTP error " + ex.getCode(), ex); - syncResult.stats.numParseExceptions++; - } else { - Log.w(TAG, "Soft HTTP error " + ex.getCode() + " (Android will try again later)", ex); - syncResult.stats.numIoExceptions++; - } - } catch (LocalStorageException ex) { - syncResult.databaseError = true; - Log.e(TAG, "Local storage (content provider) exception", ex); - } catch (IOException ex) { - syncResult.stats.numIoExceptions++; - Log.e(TAG, "I/O error (Android will try again later)", ex); - } catch (URISyntaxException ex) { - Log.e(TAG, "Invalid URI (file name) syntax", ex); - } - } finally { - // allow httpClient shutdown - httpClientLock.readLock().unlock(); - } - - Log.i(TAG, "Sync complete for " + authority); - } - -} diff --git a/src/at/bitfire/davdroid/syncadapter/GeneralSettingsActivity.java b/src/at/bitfire/davdroid/syncadapter/GeneralSettingsActivity.java deleted file mode 100644 index 9135b428..00000000 --- a/src/at/bitfire/davdroid/syncadapter/GeneralSettingsActivity.java +++ /dev/null @@ -1,54 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.preference.PreferenceFragment; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import at.bitfire.davdroid.R; - -public class GeneralSettingsActivity extends Activity { - final static String URL_REPORT_ISSUE = "https://github.com/bitfireAT/davdroid/blob/master/CONTRIBUTING.md#reporting-issues"; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getFragmentManager().beginTransaction() - .replace(android.R.id.content, new GeneralSettingsFragment()) - .commit(); - } - - public void reportIssue(MenuItem item) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(URL_REPORT_ISSUE))); - } - - - public static class GeneralSettingsFragment extends PreferenceFragment { - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getPreferenceManager().setSharedPreferencesMode(Context.MODE_MULTI_PROCESS); - addPreferencesFromResource(R.xml.general_settings); - - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.debug_settings, menu); - } - } -} diff --git a/src/at/bitfire/davdroid/syncadapter/LoginEmailFragment.java b/src/at/bitfire/davdroid/syncadapter/LoginEmailFragment.java deleted file mode 100644 index e7d2e3ec..00000000 --- a/src/at/bitfire/davdroid/syncadapter/LoginEmailFragment.java +++ /dev/null @@ -1,111 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import java.net.URI; -import java.net.URISyntaxException; - -import android.app.DialogFragment; -import android.app.Fragment; -import android.app.FragmentTransaction; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import at.bitfire.davdroid.R; - -public class LoginEmailFragment extends Fragment implements TextWatcher { - - protected EditText editEmail, editPassword; - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.login_email, container, false); - - editEmail = (EditText)v.findViewById(R.id.email_address); - editEmail.addTextChangedListener(this); - editPassword = (EditText)v.findViewById(R.id.password); - editPassword.addTextChangedListener(this); - - setHasOptionsMenu(true); - return v; - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.only_next, menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.next: - FragmentTransaction ft = getFragmentManager().beginTransaction(); - - Bundle args = new Bundle(); - String email = editEmail.getText().toString(); - args.putString(QueryServerDialogFragment.EXTRA_BASE_URI, "mailto:" + email); - args.putString(QueryServerDialogFragment.EXTRA_USER_NAME, email); - args.putString(QueryServerDialogFragment.EXTRA_PASSWORD, editPassword.getText().toString()); - args.putBoolean(QueryServerDialogFragment.EXTRA_AUTH_PREEMPTIVE, true); - - DialogFragment dialog = new QueryServerDialogFragment(); - dialog.setArguments(args); - dialog.show(ft, QueryServerDialogFragment.class.getName()); - break; - default: - return false; - } - return true; - } - - - // input validation - - @Override - public void onPrepareOptionsMenu(Menu menu) { - boolean passwordOk = editPassword.getText().length() > 0, - emailOk = false; - - String email = editEmail.getText().toString(); - try { - URI uri = new URI("mailto:" + email); - if (uri.isOpaque()) { - int pos = email.lastIndexOf("@"); - if (pos != -1) - emailOk = !email.substring(pos+1).isEmpty(); - } - } catch (URISyntaxException e) { - // invalid mailto: URI - } - - MenuItem item = menu.findItem(R.id.next); - item.setEnabled(emailOk && passwordOk); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - getActivity().invalidateOptionsMenu(); - } - - @Override - public void afterTextChanged(Editable s) { - } - -} diff --git a/src/at/bitfire/davdroid/syncadapter/LoginTypeFragment.java b/src/at/bitfire/davdroid/syncadapter/LoginTypeFragment.java deleted file mode 100644 index 8507bcc6..00000000 --- a/src/at/bitfire/davdroid/syncadapter/LoginTypeFragment.java +++ /dev/null @@ -1,57 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import android.app.Fragment; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.RadioButton; -import at.bitfire.davdroid.R; - -public class LoginTypeFragment extends Fragment { - - protected RadioButton btnTypeEmail, btnTypeURL; - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.login_type, container, false); - - btnTypeEmail = (RadioButton)v.findViewById(R.id.login_type_email); - btnTypeURL = (RadioButton)v.findViewById(R.id.login_type_url); - - setHasOptionsMenu(true); - - return v; - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.only_next, menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.next: - Fragment loginFragment = btnTypeEmail.isChecked() ? new LoginEmailFragment() : new LoginURLFragment(); - getFragmentManager().beginTransaction() - .replace(R.id.fragment_container, loginFragment) - .addToBackStack(null) - .commitAllowingStateLoss(); - return true; - default: - return false; - } - } -} diff --git a/src/at/bitfire/davdroid/syncadapter/LoginURLFragment.java b/src/at/bitfire/davdroid/syncadapter/LoginURLFragment.java deleted file mode 100644 index d504ff56..00000000 --- a/src/at/bitfire/davdroid/syncadapter/LoginURLFragment.java +++ /dev/null @@ -1,149 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import java.net.URI; -import java.net.URISyntaxException; - -import org.apache.commons.lang.StringUtils; - -import android.app.DialogFragment; -import android.app.Fragment; -import android.app.FragmentTransaction; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.TextView; -import at.bitfire.davdroid.R; -import at.bitfire.davdroid.URLUtils; - -public class LoginURLFragment extends Fragment implements TextWatcher { - protected String scheme; - - protected TextView textHttpWarning; - protected EditText editBaseURI, editUserName, editPassword; - protected CheckBox checkboxPreemptive; - protected Button btnNext; - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.login_url, container, false); - - // protocol selection spinner - textHttpWarning = (TextView) v.findViewById(R.id.http_warning); - - Spinner spnrScheme = (Spinner) v.findViewById(R.id.login_scheme); - spnrScheme.setOnItemSelectedListener(new OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - scheme = parent.getAdapter().getItem(position).toString(); - textHttpWarning.setVisibility(scheme.equals("https://") ? View.GONE : View.VISIBLE); - } - - @Override - public void onNothingSelected(AdapterView parent) { - scheme = null; - } - }); - spnrScheme.setSelection(1); // HTTPS - - // other input fields - editBaseURI = (EditText) v.findViewById(R.id.login_host_path); - editBaseURI.addTextChangedListener(this); - - editUserName = (EditText) v.findViewById(R.id.userName); - editUserName.addTextChangedListener(this); - - editPassword = (EditText) v.findViewById(R.id.password); - editPassword.addTextChangedListener(this); - - checkboxPreemptive = (CheckBox) v.findViewById(R.id.auth_preemptive); - - // hook into action bar - setHasOptionsMenu(true); - - return v; - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.only_next, menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.next: - FragmentTransaction ft = getFragmentManager().beginTransaction(); - - Bundle args = new Bundle(); - String host_path = editBaseURI.getText().toString(); - args.putString(QueryServerDialogFragment.EXTRA_BASE_URI, URLUtils.sanitize(scheme + host_path)); - args.putString(QueryServerDialogFragment.EXTRA_USER_NAME, editUserName.getText().toString()); - args.putString(QueryServerDialogFragment.EXTRA_PASSWORD, editPassword.getText().toString()); - args.putBoolean(QueryServerDialogFragment.EXTRA_AUTH_PREEMPTIVE, checkboxPreemptive.isChecked()); - - DialogFragment dialog = new QueryServerDialogFragment(); - dialog.setArguments(args); - dialog.show(ft, QueryServerDialogFragment.class.getName()); - break; - default: - return false; - } - return true; - } - - - // input validation - - @Override - public void onPrepareOptionsMenu(Menu menu) { - boolean ok = - editUserName.getText().length() > 0 && - editPassword.getText().length() > 0; - - if (ok) - // check host name - try { - URI uri = new URI(URLUtils.sanitize(scheme + editBaseURI.getText().toString())); - if (StringUtils.isBlank(uri.getHost())) - ok = false; - } catch (URISyntaxException e) { - ok = false; - } - - MenuItem item = menu.findItem(R.id.next); - item.setEnabled(ok); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - getActivity().invalidateOptionsMenu(); - } - - @Override - public void afterTextChanged(Editable s) { - } -} diff --git a/src/at/bitfire/davdroid/syncadapter/QueryServerDialogFragment.java b/src/at/bitfire/davdroid/syncadapter/QueryServerDialogFragment.java deleted file mode 100644 index 2596cfe0..00000000 --- a/src/at/bitfire/davdroid/syncadapter/QueryServerDialogFragment.java +++ /dev/null @@ -1,129 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; - -import lombok.Cleanup; -import android.app.DialogFragment; -import android.app.LoaderManager.LoaderCallbacks; -import android.content.AsyncTaskLoader; -import android.content.Context; -import android.content.Loader; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ProgressBar; -import android.widget.Toast; -import at.bitfire.davdroid.R; -import at.bitfire.davdroid.resource.DavResourceFinder; -import at.bitfire.davdroid.resource.ServerInfo; -import at.bitfire.davdroid.webdav.DavException; -import ch.boye.httpclientandroidlib.HttpException; - -public class QueryServerDialogFragment extends DialogFragment implements LoaderCallbacks { - private static final String TAG = "davdroid.QueryServerDialogFragment"; - public static final String - EXTRA_BASE_URI = "base_uri", - EXTRA_USER_NAME = "user_name", - EXTRA_PASSWORD = "password", - EXTRA_AUTH_PREEMPTIVE = "auth_preemptive"; - - ProgressBar progressBar; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setStyle(DialogFragment.STYLE_NO_TITLE, android.R.style.Theme_Holo_Light_Dialog); - setCancelable(false); - - Loader loader = getLoaderManager().initLoader(0, getArguments(), this); - if (savedInstanceState == null) // http://code.google.com/p/android/issues/detail?id=14944 - loader.forceLoad(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.query_server, container, false); - return v; - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - Log.i(TAG, "onCreateLoader"); - return new ServerInfoLoader(getActivity(), args); - } - - @Override - public void onLoadFinished(Loader loader, ServerInfo serverInfo) { - if (serverInfo.getErrorMessage() != null) - Toast.makeText(getActivity(), serverInfo.getErrorMessage(), Toast.LENGTH_LONG).show(); - else { - SelectCollectionsFragment selectCollections = new SelectCollectionsFragment(); - Bundle arguments = new Bundle(); - arguments.putSerializable(SelectCollectionsFragment.KEY_SERVER_INFO, serverInfo); - selectCollections.setArguments(arguments); - - getFragmentManager().beginTransaction() - .replace(R.id.fragment_container, selectCollections) - .addToBackStack(null) - .commitAllowingStateLoss(); - } - - getDialog().dismiss(); - } - - @Override - public void onLoaderReset(Loader arg0) { - } - - - static class ServerInfoLoader extends AsyncTaskLoader { - private static final String TAG = "davdroid.ServerInfoLoader"; - final Bundle args; - final Context context; - - public ServerInfoLoader(Context context, Bundle args) { - super(context); - this.context = context; - this.args = args; - } - - @Override - public ServerInfo loadInBackground() { - ServerInfo serverInfo = new ServerInfo( - URI.create(args.getString(EXTRA_BASE_URI)), - args.getString(EXTRA_USER_NAME), - args.getString(EXTRA_PASSWORD), - args.getBoolean(EXTRA_AUTH_PREEMPTIVE) - ); - - try { - @Cleanup DavResourceFinder finder = new DavResourceFinder(context); - finder.findResources(serverInfo); - } catch (URISyntaxException e) { - serverInfo.setErrorMessage(getContext().getString(R.string.exception_uri_syntax, e.getMessage())); - } catch (IOException e) { - serverInfo.setErrorMessage(getContext().getString(R.string.exception_io, e.getLocalizedMessage())); - } catch (HttpException e) { - Log.e(TAG, "HTTP error while querying server info", e); - serverInfo.setErrorMessage(getContext().getString(R.string.exception_http, e.getLocalizedMessage())); - } catch (DavException e) { - Log.e(TAG, "DAV error while querying server info", e); - serverInfo.setErrorMessage(getContext().getString(R.string.exception_incapable_resource, e.getLocalizedMessage())); - } - - return serverInfo; - } - - } -} diff --git a/src/at/bitfire/davdroid/syncadapter/SelectCollectionsAdapter.java b/src/at/bitfire/davdroid/syncadapter/SelectCollectionsAdapter.java deleted file mode 100644 index 95c871ed..00000000 --- a/src/at/bitfire/davdroid/syncadapter/SelectCollectionsAdapter.java +++ /dev/null @@ -1,161 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import lombok.Getter; -import android.annotation.SuppressLint; -import android.content.Context; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.CheckedTextView; -import android.widget.ListAdapter; -import at.bitfire.davdroid.R; -import at.bitfire.davdroid.resource.ServerInfo; -import at.bitfire.davdroid.resource.ServerInfo.ResourceInfo.Type; - -public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter { - final static int TYPE_ADDRESS_BOOKS_HEADING = 0, - TYPE_ADDRESS_BOOKS_ROW = 1, - TYPE_CALENDARS_HEADING = 2, - TYPE_CALENDARS_ROW = 3; - - protected Context context; - protected ServerInfo serverInfo; - @Getter protected int nAddressBooks, nCalendars; - - - public SelectCollectionsAdapter(Context context, ServerInfo serverInfo) { - this.context = context; - - this.serverInfo = serverInfo; - nAddressBooks = (serverInfo.getAddressBooks() == null) ? 0 : serverInfo.getAddressBooks().size(); - nCalendars = (serverInfo.getCalendars() == null) ? 0 : serverInfo.getCalendars().size(); - } - - - // item data - - @Override - public int getCount() { - return nAddressBooks + nCalendars + 2; - } - - @Override - public Object getItem(int position) { - if (position > 0 && position <= nAddressBooks) - return serverInfo.getAddressBooks().get(position - 1); - else if (position > nAddressBooks + 1) - return serverInfo.getCalendars().get(position - nAddressBooks - 2); - return null; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public long getItemId(int position) { - return position; - } - - - // item views - - @Override - public int getViewTypeCount() { - return 4; - } - - @Override - public int getItemViewType(int position) { - if (position == 0) - return TYPE_ADDRESS_BOOKS_HEADING; - else if (position <= nAddressBooks) - return TYPE_ADDRESS_BOOKS_ROW; - else if (position == nAddressBooks + 1) - return TYPE_CALENDARS_HEADING; - else if (position <= nAddressBooks + nCalendars + 1) - return TYPE_CALENDARS_ROW; - else - return IGNORE_ITEM_VIEW_TYPE; - } - - @Override - @SuppressLint("InflateParams") - public View getView(int position, View convertView, ViewGroup parent) { - View v = convertView; - - // step 1: get view (either by creating or recycling) - if (v == null) { - LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - switch (getItemViewType(position)) { - case TYPE_ADDRESS_BOOKS_HEADING: - v = inflater.inflate(R.layout.address_books_heading, parent, false); - break; - case TYPE_ADDRESS_BOOKS_ROW: - v = inflater.inflate(android.R.layout.simple_list_item_single_choice, null); - v.setPadding(0, 8, 0, 8); - break; - case TYPE_CALENDARS_HEADING: - v = inflater.inflate(R.layout.calendars_heading, parent, false); - break; - case TYPE_CALENDARS_ROW: - v = inflater.inflate(android.R.layout.simple_list_item_multiple_choice, null); - v.setPadding(0, 8, 0, 8); - } - } - - // step 2: fill view with content - switch (getItemViewType(position)) { - case TYPE_ADDRESS_BOOKS_ROW: - setContent((CheckedTextView)v, R.drawable.addressbook, (ServerInfo.ResourceInfo)getItem(position)); - break; - case TYPE_CALENDARS_ROW: - setContent((CheckedTextView)v, R.drawable.calendar, (ServerInfo.ResourceInfo)getItem(position)); - } - - return v; - } - - protected void setContent(CheckedTextView view, int collectionIcon, ServerInfo.ResourceInfo info) { - // set layout and icons - view.setCompoundDrawablesWithIntrinsicBounds(collectionIcon, 0, info.isReadOnly() ? R.drawable.ic_read_only : 0, 0); - view.setCompoundDrawablePadding(10); - - // set text - String title = info.getTitle(); - if (title == null) // unnamed collection - title = context.getString((info.getType() == Type.ADDRESS_BOOK) ? - R.string.setup_address_book : R.string.setup_calendar); - title = "" + title + ""; - if (info.isReadOnly()) - title = title + " (" + context.getString(R.string.setup_read_only) + ")"; - - String description = info.getDescription(); - if (description == null) - description = info.getURL(); - - // FIXME escape HTML - view.setText(Html.fromHtml(title + "
" + description)); - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int position) { - int type = getItemViewType(position); - return (type == TYPE_ADDRESS_BOOKS_ROW || type == TYPE_CALENDARS_ROW); - } -} diff --git a/src/at/bitfire/davdroid/syncadapter/SelectCollectionsFragment.java b/src/at/bitfire/davdroid/syncadapter/SelectCollectionsFragment.java deleted file mode 100644 index c7a044b2..00000000 --- a/src/at/bitfire/davdroid/syncadapter/SelectCollectionsFragment.java +++ /dev/null @@ -1,127 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import android.app.ListFragment; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ListAdapter; -import android.widget.ListView; -import at.bitfire.davdroid.R; -import at.bitfire.davdroid.resource.ServerInfo; - -public class SelectCollectionsFragment extends ListFragment { - public static final String KEY_SERVER_INFO = "server_info"; - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = super.onCreateView(inflater, container, savedInstanceState); - setHasOptionsMenu(true); - return v; - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - setListAdapter(null); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - final ListView listView = getListView(); - listView.setPadding(20, 30, 20, 30); - - View header = getActivity().getLayoutInflater().inflate(R.layout.select_collections_header, getListView(), false); - listView.addHeaderView(header, getListView(), false); - - final ServerInfo serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO); - final SelectCollectionsAdapter adapter = new SelectCollectionsAdapter(view.getContext(), serverInfo); - setListAdapter(adapter); - - listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - listView.setOnItemClickListener(new OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - int itemPosition = position - 1; // one list header view at pos. 0 - if (adapter.getItemViewType(itemPosition) == SelectCollectionsAdapter.TYPE_ADDRESS_BOOKS_ROW) { - // unselect all other address books - for (int pos = 1; pos <= adapter.getNAddressBooks(); pos++) - if (pos != itemPosition) - listView.setItemChecked(pos + 1, false); - } - - getActivity().invalidateOptionsMenu(); - } - }); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.only_next, menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.next: - ServerInfo serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO); - - // synchronize only selected collections - for (ServerInfo.ResourceInfo addressBook : serverInfo.getAddressBooks()) - addressBook.setEnabled(false); - for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars()) - calendar.setEnabled(false); - - ListAdapter adapter = getListView().getAdapter(); - for (long id : getListView().getCheckedItemIds()) { - int position = (int)id + 1; // +1 because header view is inserted at pos. 0 - ServerInfo.ResourceInfo info = (ServerInfo.ResourceInfo)adapter.getItem(position); - info.setEnabled(true); - } - - // pass to "account details" fragment - AccountDetailsFragment accountDetails = new AccountDetailsFragment(); - Bundle arguments = new Bundle(); - arguments.putSerializable(SelectCollectionsFragment.KEY_SERVER_INFO, serverInfo); - accountDetails.setArguments(arguments); - - getFragmentManager().beginTransaction() - .replace(R.id.fragment_container, accountDetails) - .addToBackStack(null) - .commitAllowingStateLoss(); - break; - default: - return false; - } - return true; - } - - - // input validation - - @Override - public void onPrepareOptionsMenu(Menu menu) { - boolean ok = false; - try { - ok = getListView().getCheckedItemCount() > 0; - } catch(IllegalStateException e) { - } - MenuItem item = menu.findItem(R.id.next); - item.setEnabled(ok); - } -} diff --git a/src/at/bitfire/davdroid/syncadapter/SyncManager.java b/src/at/bitfire/davdroid/syncadapter/SyncManager.java deleted file mode 100644 index c1bf2fa2..00000000 --- a/src/at/bitfire/davdroid/syncadapter/SyncManager.java +++ /dev/null @@ -1,214 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.HashSet; -import java.util.Set; - -import net.fortuna.ical4j.model.ValidationException; -import android.content.SyncResult; -import android.util.Log; -import at.bitfire.davdroid.ArrayUtils; -import at.bitfire.davdroid.resource.LocalCollection; -import at.bitfire.davdroid.resource.LocalStorageException; -import at.bitfire.davdroid.resource.RecordNotFoundException; -import at.bitfire.davdroid.resource.RemoteCollection; -import at.bitfire.davdroid.resource.Resource; -import at.bitfire.davdroid.webdav.DavException; -import at.bitfire.davdroid.webdav.HttpException; -import at.bitfire.davdroid.webdav.NotFoundException; -import at.bitfire.davdroid.webdav.PreconditionFailedException; - -public class SyncManager { - private static final String TAG = "davdroid.SyncManager"; - - private static final int MAX_MULTIGET_RESOURCES = 35; - - protected LocalCollection local; - protected RemoteCollection remote; - - - public SyncManager(LocalCollection local, RemoteCollection remote) { - this.local = local; - this.remote = remote; - } - - - public void synchronize(boolean manualSync, SyncResult syncResult) throws URISyntaxException, LocalStorageException, IOException, HttpException, DavException { - // PHASE 1: push local changes to server - int deletedRemotely = pushDeleted(), - addedRemotely = pushNew(), - updatedRemotely = pushDirty(); - - syncResult.stats.numEntries = deletedRemotely + addedRemotely + updatedRemotely; - - // PHASE 2A: check if there's a reason to do a sync with remote (= forced sync or remote CTag changed) - boolean fetchCollection = syncResult.stats.numEntries > 0; - if (manualSync) { - Log.i(TAG, "Synchronization forced"); - fetchCollection = true; - } - if (!fetchCollection) { - String currentCTag = remote.getCTag(), - lastCTag = local.getCTag(); - Log.d(TAG, "Last local CTag = " + lastCTag + "; current remote CTag = " + currentCTag); - if (currentCTag == null || !currentCTag.equals(lastCTag)) - fetchCollection = true; - } - - if (!fetchCollection) { - Log.i(TAG, "No local changes and CTags match, no need to sync"); - return; - } - - // PHASE 2B: detect details of remote changes - Log.i(TAG, "Fetching remote resource list"); - Set remotelyAdded = new HashSet(), - remotelyUpdated = new HashSet(); - - Resource[] remoteResources = remote.getMemberETags(); - for (Resource remoteResource : remoteResources) { - try { - Resource localResource = local.findByRemoteName(remoteResource.getName(), false); - if (localResource.getETag() == null || !localResource.getETag().equals(remoteResource.getETag())) - remotelyUpdated.add(remoteResource); - } catch(RecordNotFoundException e) { - remotelyAdded.add(remoteResource); - } - } - - // PHASE 3: pull remote changes from server - syncResult.stats.numInserts = pullNew(remotelyAdded.toArray(new Resource[0])); - syncResult.stats.numUpdates = pullChanged(remotelyUpdated.toArray(new Resource[0])); - syncResult.stats.numEntries += syncResult.stats.numInserts + syncResult.stats.numUpdates; - - Log.i(TAG, "Removing non-dirty resources that are not present remotely anymore"); - local.deleteAllExceptRemoteNames(remoteResources); - local.commit(); - - // update collection CTag - Log.i(TAG, "Sync complete, fetching new CTag"); - local.setCTag(remote.getCTag()); - } - - - private int pushDeleted() throws URISyntaxException, LocalStorageException, IOException, HttpException { - int count = 0; - long[] deletedIDs = local.findDeleted(); - - try { - Log.i(TAG, "Remotely removing " + deletedIDs.length + " deleted resource(s) (if not changed)"); - for (long id : deletedIDs) - try { - Resource res = local.findById(id, false); - if (res.getName() != null) // is this resource even present remotely? - try { - remote.delete(res); - } catch(NotFoundException e) { - Log.i(TAG, "Locally-deleted resource has already been removed from server"); - } catch(PreconditionFailedException e) { - Log.i(TAG, "Locally-deleted resource has been changed on the server in the meanwhile"); - } - - // always delete locally so that the record with the DELETED flag doesn't cause another deletion attempt - local.delete(res); - - count++; - } catch (RecordNotFoundException e) { - Log.wtf(TAG, "Couldn't read locally-deleted record", e); - } - } finally { - local.commit(); - } - return count; - } - - private int pushNew() throws URISyntaxException, LocalStorageException, IOException, HttpException { - int count = 0; - long[] newIDs = local.findNew(); - Log.i(TAG, "Uploading " + newIDs.length + " new resource(s) (if not existing)"); - try { - for (long id : newIDs) - try { - Resource res = local.findById(id, true); - String eTag = remote.add(res); - if (eTag != null) - local.updateETag(res, eTag); - local.clearDirty(res); - count++; - } catch(PreconditionFailedException e) { - Log.i(TAG, "Didn't overwrite existing resource with other content"); - } catch (ValidationException e) { - Log.e(TAG, "Couldn't create entity for adding: " + e.toString()); - } catch (RecordNotFoundException e) { - Log.wtf(TAG, "Couldn't read new record", e); - } - } finally { - local.commit(); - } - return count; - } - - private int pushDirty() throws URISyntaxException, LocalStorageException, IOException, HttpException { - int count = 0; - long[] dirtyIDs = local.findUpdated(); - Log.i(TAG, "Uploading " + dirtyIDs.length + " modified resource(s) (if not changed)"); - try { - for (long id : dirtyIDs) { - try { - Resource res = local.findById(id, true); - String eTag = remote.update(res); - if (eTag != null) - local.updateETag(res, eTag); - local.clearDirty(res); - count++; - } catch(PreconditionFailedException e) { - Log.i(TAG, "Locally changed resource has been changed on the server in the meanwhile"); - } catch (ValidationException e) { - Log.e(TAG, "Couldn't create entity for updating: " + e.toString()); - } catch (RecordNotFoundException e) { - Log.e(TAG, "Couldn't read dirty record", e); - } - } - } finally { - local.commit(); - } - return count; - } - - private int pullNew(Resource[] resourcesToAdd) throws URISyntaxException, LocalStorageException, IOException, HttpException, DavException { - int count = 0; - Log.i(TAG, "Fetching " + resourcesToAdd.length + " new remote resource(s)"); - - for (Resource[] resources : ArrayUtils.partition(resourcesToAdd, MAX_MULTIGET_RESOURCES)) - for (Resource res : remote.multiGet(resources)) { - Log.d(TAG, "Adding " + res.getName()); - local.add(res); - local.commit(); - count++; - } - return count; - } - - private int pullChanged(Resource[] resourcesToUpdate) throws URISyntaxException, LocalStorageException, IOException, HttpException, DavException { - int count = 0; - Log.i(TAG, "Fetching " + resourcesToUpdate.length + " updated remote resource(s)"); - - for (Resource[] resources : ArrayUtils.partition(resourcesToUpdate, MAX_MULTIGET_RESOURCES)) - for (Resource res : remote.multiGet(resources)) { - Log.i(TAG, "Updating " + res.getName()); - local.updateByRemoteName(res); - local.commit(); - count++; - } - return count; - } - -} diff --git a/src/at/bitfire/davdroid/syncadapter/WebDavResourceAdapter.java b/src/at/bitfire/davdroid/syncadapter/WebDavResourceAdapter.java deleted file mode 100644 index 9263874d..00000000 --- a/src/at/bitfire/davdroid/syncadapter/WebDavResourceAdapter.java +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.syncadapter; - -import java.util.List; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.TextView; -import at.bitfire.davdroid.webdav.WebDavResource; - -public class WebDavResourceAdapter extends BaseAdapter { - protected int viewId; - protected LayoutInflater inflater; - WebDavResource[] items; - - public WebDavResourceAdapter(Context context, int textViewResourceId, List objects) { - viewId = textViewResourceId; - inflater = LayoutInflater.from(context); - items = objects.toArray(new WebDavResource[0]); - } - - @Override - public View getView(int position, View view, ViewGroup parent) { - WebDavResource item = items[position]; - View itemView = (View)inflater.inflate(viewId, null); - - TextView textName = (TextView) itemView.findViewById(android.R.id.text1); - textName.setText(item.getDisplayName()); - - TextView textDescription = (TextView) itemView.findViewById(android.R.id.text2); - String description = item.getDescription(); - if (description == null) - description = item.getLocation().getPath(); - textDescription.setText(description); - - return itemView; - } - - @Override - public int getCount() { - return items.length; - } - - @Override - public Object getItem(int position) { - return items[position]; - } - - @Override - public long getItemId(int position) { - return position; - } -} diff --git a/src/at/bitfire/davdroid/webdav/DavAddressbookMultiget.java b/src/at/bitfire/davdroid/webdav/DavAddressbookMultiget.java deleted file mode 100644 index 9b32960f..00000000 --- a/src/at/bitfire/davdroid/webdav/DavAddressbookMultiget.java +++ /dev/null @@ -1,21 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import org.simpleframework.xml.Namespace; -import org.simpleframework.xml.NamespaceList; -import org.simpleframework.xml.Root; - -@Root(name="addressbook-multiget") -@NamespaceList({ - @Namespace(reference="DAV:"), - @Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav") -}) -@Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav") -public class DavAddressbookMultiget extends DavMultiget { -} diff --git a/src/at/bitfire/davdroid/webdav/DavCalendarMultiget.java b/src/at/bitfire/davdroid/webdav/DavCalendarMultiget.java deleted file mode 100644 index 75a7ce71..00000000 --- a/src/at/bitfire/davdroid/webdav/DavCalendarMultiget.java +++ /dev/null @@ -1,21 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import org.simpleframework.xml.Namespace; -import org.simpleframework.xml.NamespaceList; -import org.simpleframework.xml.Root; - -@Root(name="calendar-multiget") -@NamespaceList({ - @Namespace(reference="DAV:"), - @Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav") -}) -@Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav") -public class DavCalendarMultiget extends DavMultiget { -} diff --git a/src/at/bitfire/davdroid/webdav/DavException.java b/src/at/bitfire/davdroid/webdav/DavException.java deleted file mode 100644 index 506af6da..00000000 --- a/src/at/bitfire/davdroid/webdav/DavException.java +++ /dev/null @@ -1,25 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -public class DavException extends Exception { - private static final long serialVersionUID = -2118919144443165706L; - - final private static String prefix = "Invalid DAV response: "; - - /* used to indiciate DAV protocol errors */ - - - public DavException(String message) { - super(prefix + message); - } - - public DavException(String message, Throwable ex) { - super(prefix + message, ex); - } -} diff --git a/src/at/bitfire/davdroid/webdav/DavHref.java b/src/at/bitfire/davdroid/webdav/DavHref.java deleted file mode 100644 index 438c4e49..00000000 --- a/src/at/bitfire/davdroid/webdav/DavHref.java +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import org.simpleframework.xml.Namespace; -import org.simpleframework.xml.Root; -import org.simpleframework.xml.Text; - -@Root(name="href") -@Namespace(prefix="D",reference="DAV:") -public class DavHref { - @Text - String href; - - DavHref() { - } - - public DavHref(String href) { - this.href = href; - } -} diff --git a/src/at/bitfire/davdroid/webdav/DavHttpClient.java b/src/at/bitfire/davdroid/webdav/DavHttpClient.java deleted file mode 100644 index b4d1ee16..00000000 --- a/src/at/bitfire/davdroid/webdav/DavHttpClient.java +++ /dev/null @@ -1,72 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import android.util.Log; -import at.bitfire.davdroid.Constants; -import ch.boye.httpclientandroidlib.client.config.RequestConfig; -import ch.boye.httpclientandroidlib.config.Registry; -import ch.boye.httpclientandroidlib.config.RegistryBuilder; -import ch.boye.httpclientandroidlib.conn.socket.ConnectionSocketFactory; -import ch.boye.httpclientandroidlib.conn.socket.PlainConnectionSocketFactory; -import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient; -import ch.boye.httpclientandroidlib.impl.client.HttpClientBuilder; -import ch.boye.httpclientandroidlib.impl.client.HttpClients; -import ch.boye.httpclientandroidlib.impl.conn.ManagedHttpClientConnectionFactory; -import ch.boye.httpclientandroidlib.impl.conn.PoolingHttpClientConnectionManager; - -public class DavHttpClient { - private final static String TAG = "davdroid.DavHttpClient"; - - private final static RequestConfig defaultRqConfig; - private final static Registry socketFactoryRegistry; - - static { - socketFactoryRegistry = RegistryBuilder. create() - .register("http", PlainConnectionSocketFactory.getSocketFactory()) - .register("https", TlsSniSocketFactory.INSTANCE) - .build(); - - // use request defaults from AndroidHttpClient - defaultRqConfig = RequestConfig.copy(RequestConfig.DEFAULT) - .setConnectTimeout(20*1000) - .setSocketTimeout(45*1000) - .setStaleConnectionCheckEnabled(false) - .build(); - } - - - public static CloseableHttpClient create(boolean disableCompression, boolean logTraffic) { - PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); - // limits per DavHttpClient (= per DavSyncAdapter extends AbstractThreadedSyncAdapter) - connectionManager.setMaxTotal(3); // max. 3 connections in total - connectionManager.setDefaultMaxPerRoute(2); // max. 2 connections per host - - HttpClientBuilder builder = HttpClients.custom() - .useSystemProperties() - .setConnectionManager(connectionManager) - .setDefaultRequestConfig(defaultRqConfig) - .setRetryHandler(DavHttpRequestRetryHandler.INSTANCE) - .setRedirectStrategy(DavRedirectStrategy.INSTANCE) - .setUserAgent("DAVdroid/" + Constants.APP_VERSION) - .disableCookieManagement(); - - if (disableCompression) { - Log.d(TAG, "Disabling compression for debugging purposes"); - builder = builder.disableContentCompression(); - } - - if (logTraffic) - Log.d(TAG, "Logging network traffic for debugging purposes"); - ManagedHttpClientConnectionFactory.INSTANCE.wirelog.enableDebug(logTraffic); - ManagedHttpClientConnectionFactory.INSTANCE.log.enableDebug(logTraffic); - - return builder.build(); - } - -} diff --git a/src/at/bitfire/davdroid/webdav/DavHttpRequestRetryHandler.java b/src/at/bitfire/davdroid/webdav/DavHttpRequestRetryHandler.java deleted file mode 100644 index 6ab2a509..00000000 --- a/src/at/bitfire/davdroid/webdav/DavHttpRequestRetryHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import java.util.Locale; - -import org.apache.commons.lang.ArrayUtils; - -import ch.boye.httpclientandroidlib.HttpRequest; -import ch.boye.httpclientandroidlib.impl.client.DefaultHttpRequestRetryHandler; - -public class DavHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler { - final static DavHttpRequestRetryHandler INSTANCE = new DavHttpRequestRetryHandler(); - - // see http://www.iana.org/assignments/http-methods/http-methods.xhtml - private final static String idempotentMethods[] = { - "DELETE", "GET", "HEAD", "MKCALENDAR", "MKCOL", "OPTIONS", "PROPFIND", "PROPPATCH", - "PUT", "REPORT", "SEARCH", "TRACE" - }; - - public DavHttpRequestRetryHandler() { - super(/* retry count */ 3, /* retry already sent requests? */ false); - } - - @Override - protected boolean handleAsIdempotent(final HttpRequest request) { - final String method = request.getRequestLine().getMethod().toUpperCase(Locale.ROOT); - return ArrayUtils.contains(idempotentMethods, method); - } -} diff --git a/src/at/bitfire/davdroid/webdav/DavIncapableException.java b/src/at/bitfire/davdroid/webdav/DavIncapableException.java deleted file mode 100644 index 794865ec..00000000 --- a/src/at/bitfire/davdroid/webdav/DavIncapableException.java +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -public class DavIncapableException extends DavException { - private static final long serialVersionUID = -7199786680939975667L; - - /* used to indicate that the server doesn't support DAV */ - - public DavIncapableException(String msg) { - super(msg); - } -} diff --git a/src/at/bitfire/davdroid/webdav/DavMultiget.java b/src/at/bitfire/davdroid/webdav/DavMultiget.java deleted file mode 100644 index 64162b19..00000000 --- a/src/at/bitfire/davdroid/webdav/DavMultiget.java +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import java.util.ArrayList; -import java.util.List; - -import org.simpleframework.xml.Element; -import org.simpleframework.xml.ElementList; -import org.simpleframework.xml.Order; - -@Order(elements={"prop","href"}) -public class DavMultiget { - public enum Type { - ADDRESS_BOOK, - CALENDAR - } - - @Element - DavProp prop; - - @ElementList(inline=true) - List hrefs; - - - public static DavMultiget newRequest(Type type, String names[]) { - DavMultiget multiget = (type == Type.ADDRESS_BOOK) ? new DavAddressbookMultiget() : new DavCalendarMultiget(); - - 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(); - - multiget.hrefs = new ArrayList(names.length); - for (String name : names) - multiget.hrefs.add(new DavHref(name)); - - return multiget; - } -} diff --git a/src/at/bitfire/davdroid/webdav/DavMultistatus.java b/src/at/bitfire/davdroid/webdav/DavMultistatus.java deleted file mode 100644 index dce27c2b..00000000 --- a/src/at/bitfire/davdroid/webdav/DavMultistatus.java +++ /dev/null @@ -1,21 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import java.util.List; - -import org.simpleframework.xml.ElementList; -import org.simpleframework.xml.Namespace; -import org.simpleframework.xml.Root; - -@Namespace(reference="DAV:") -@Root(strict=false) -public class DavMultistatus { - @ElementList(inline=true,entry="response",required=false) - List response; -} diff --git a/src/at/bitfire/davdroid/webdav/DavNoContentException.java b/src/at/bitfire/davdroid/webdav/DavNoContentException.java deleted file mode 100644 index 6e3464b9..00000000 --- a/src/at/bitfire/davdroid/webdav/DavNoContentException.java +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -public class DavNoContentException extends DavException { - private static final long serialVersionUID = 6256645020350945477L; - - private final static String message = "HTTP response entity (content) expected but not received"; - - public DavNoContentException() { - super(message); - } -} diff --git a/src/at/bitfire/davdroid/webdav/DavNoMultiStatusException.java b/src/at/bitfire/davdroid/webdav/DavNoMultiStatusException.java deleted file mode 100644 index 07eb6e18..00000000 --- a/src/at/bitfire/davdroid/webdav/DavNoMultiStatusException.java +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -public class DavNoMultiStatusException extends DavException { - private static final long serialVersionUID = -3600405724694229828L; - - private final static String message = "207 Multi-Status expected but not received"; - - public DavNoMultiStatusException() { - super(message); - } -} diff --git a/src/at/bitfire/davdroid/webdav/DavProp.java b/src/at/bitfire/davdroid/webdav/DavProp.java deleted file mode 100644 index 93f25645..00000000 --- a/src/at/bitfire/davdroid/webdav/DavProp.java +++ /dev/null @@ -1,211 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import java.util.List; - -import lombok.Getter; - -import org.simpleframework.xml.Attribute; -import org.simpleframework.xml.Element; -import org.simpleframework.xml.ElementList; -import org.simpleframework.xml.Namespace; -import org.simpleframework.xml.Root; -import org.simpleframework.xml.Text; - -@Namespace(prefix="D",reference="DAV:") -@Root(strict=false) -public class DavProp { - - /* RFC 4918 WebDAV */ - - @Element(required=false) - ResourceType resourcetype; - - @Element(required=false) - DisplayName displayname; - - @Element(required=false) - GetCTag getctag; - - @Element(required=false) - GetETag getetag; - - @Root(strict=false) - public static class ResourceType { - @Element(required=false) - @Getter private Collection collection; - public static class Collection { } - - @Element(required=false) - @Getter private Addressbook addressbook; - @Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav") - public static class Addressbook { } - - @Element(required=false) - @Getter private Calendar calendar; - @Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav") - public static class Calendar { } - } - - public static class DisplayName { - @Text(required=false) - @Getter private String displayName; - } - - @Namespace(prefix="CS",reference="http://calendarserver.org/ns/") - public static class GetCTag { - @Text(required=false) - @Getter private String CTag; - } - - public static class GetETag { - @Text(required=false) - @Getter private String ETag; - } - - - /* RFC 5397 WebDAV Current Principal Extension */ - - @Element(required=false,name="current-user-principal") - CurrentUserPrincipal currentUserPrincipal; - - public static class CurrentUserPrincipal { - @Element(required=false) - @Getter private DavHref href; - } - - - /* RFC 3744 WebDAV Access Control Protocol */ - - @ElementList(required=false,name="current-user-privilege-set",entry="privilege") - List currentUserPrivilegeSet; - - public static class Privilege { - @Element(required=false) - @Getter private PrivAll all; - - @Element(required=false) - @Getter private PrivBind bind; - - @Element(required=false) - @Getter private PrivUnbind unbind; - - @Element(required=false) - @Getter private PrivWrite write; - - @Element(required=false,name="write-content") - @Getter private PrivWriteContent writeContent; - - public static class PrivAll { } - public static class PrivBind { } - public static class PrivUnbind { } - public static class PrivWrite { } - public static class PrivWriteContent { } - } - - - - /* RFC 4791 CalDAV, RFC 6352 CardDAV */ - - @Element(required=false,name="addressbook-home-set") - AddressbookHomeSet addressbookHomeSet; - - @Element(required=false,name="calendar-home-set") - CalendarHomeSet calendarHomeSet; - - @Element(required=false,name="addressbook-description") - AddressbookDescription addressbookDescription; - - @Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav") - @ElementList(required=false,name="supported-address-data",entry="address-data-type") - List supportedAddressData; - - @Element(required=false,name="calendar-description") - CalendarDescription calendarDescription; - - @Element(required=false,name="calendar-color") - CalendarColor calendarColor; - - @Element(required=false,name="calendar-timezone") - CalendarTimezone calendarTimezone; - - @Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav") - @ElementList(required=false,name="supported-calendar-component-set",entry="comp") - List supportedCalendarComponentSet; - - @Element(name="address-data",required=false) - AddressData addressData; - - @Element(name="calendar-data",required=false) - CalendarData calendarData; - - - @Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav") - public static class AddressbookHomeSet { - @Element(required=false) - @Getter private DavHref href; - } - - @Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav") - public static class CalendarHomeSet { - @Element(required=false) - @Getter private DavHref href; - } - - @Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav") - public static class AddressbookDescription { - @Text(required=false) - @Getter private String description; - } - - @Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav") - public static class AddressDataType { - @Attribute(name="content-type") - @Getter private String contentType; - - @Attribute - @Getter private String version; - } - - @Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav") - public static class CalendarDescription { - @Text(required=false) - @Getter private String description; - } - - @Namespace(prefix="A",reference="http://apple.com/ns/ical/") - public static class CalendarColor { - @Text(required=false) - @Getter private String color; - } - - @Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav") - public static class CalendarTimezone { - @Text(required=false) - @Getter private String timezone; - } - - @Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav") - public static class Comp { - @Attribute - @Getter String name; - } - - @Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav") - public static class AddressData { - @Text(required=false) - @Getter String vcard; - } - - @Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav") - public static class CalendarData { - @Text(required=false) - @Getter String ical; - } -} diff --git a/src/at/bitfire/davdroid/webdav/DavPropfind.java b/src/at/bitfire/davdroid/webdav/DavPropfind.java deleted file mode 100644 index abf4104e..00000000 --- a/src/at/bitfire/davdroid/webdav/DavPropfind.java +++ /dev/null @@ -1,19 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Namespace; -import org.simpleframework.xml.Root; - -@Namespace(reference="DAV:") -@Root(name="propfind") -public class DavPropfind { - @Element(required=false) - protected DavProp prop; -} diff --git a/src/at/bitfire/davdroid/webdav/DavPropstat.java b/src/at/bitfire/davdroid/webdav/DavPropstat.java deleted file mode 100644 index 74ee66d6..00000000 --- a/src/at/bitfire/davdroid/webdav/DavPropstat.java +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import org.simpleframework.xml.Element; -import org.simpleframework.xml.Root; - -@Root(strict=false,name="propstat") -public class DavPropstat { - @Element - DavProp prop; - - @Element - String status; -} diff --git a/src/at/bitfire/davdroid/webdav/DavRedirectStrategy.java b/src/at/bitfire/davdroid/webdav/DavRedirectStrategy.java deleted file mode 100644 index 7019d06c..00000000 --- a/src/at/bitfire/davdroid/webdav/DavRedirectStrategy.java +++ /dev/null @@ -1,109 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; - -import android.util.Log; -import ch.boye.httpclientandroidlib.Header; -import ch.boye.httpclientandroidlib.HttpHost; -import ch.boye.httpclientandroidlib.HttpRequest; -import ch.boye.httpclientandroidlib.HttpResponse; -import ch.boye.httpclientandroidlib.ProtocolException; -import ch.boye.httpclientandroidlib.RequestLine; -import ch.boye.httpclientandroidlib.client.RedirectStrategy; -import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; -import ch.boye.httpclientandroidlib.client.methods.RequestBuilder; -import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; -import ch.boye.httpclientandroidlib.client.utils.URIUtils; -import ch.boye.httpclientandroidlib.protocol.HttpContext; - -/** - * Custom Redirect Strategy that handles 30x for CalDAV/CardDAV-specific requests correctly - */ -public class DavRedirectStrategy implements RedirectStrategy { - private final static String TAG = "davdroid.DavRedirectStrategy"; - final static DavRedirectStrategy INSTANCE = new DavRedirectStrategy(); - - protected final static String REDIRECTABLE_METHODS[] = { - "OPTIONS", "GET", "PUT", "DELETE" - }; - - - @Override - public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException { - RequestLine line = request.getRequestLine(); - - String location = getLocation(request, response, context).toString(); - Log.i(TAG, "Following redirection: " + line.getMethod() + " " + line.getUri() + " -> " + location); - - return RequestBuilder.copy(request) - .setUri(location) - .removeHeaders("Content-Length") // Content-Length will be set again automatically, if required; - // remove it now to avoid duplicate header - .build(); - } - - /** - * Determines whether a response indicates a redirection and if it does, whether to follow this redirection. - * PROPFIND and REPORT must handle redirections explicitely because multi-status processing requires knowledge of the content location. - * @return true for 3xx responses on OPTIONS, GET, PUT, DELETE requests that have a valid Location header; false otherwise - */ - @Override - public boolean isRedirected(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException { - if (response.getStatusLine().getStatusCode()/100 == 3) { - boolean redirectable = false; - for (String method : REDIRECTABLE_METHODS) - if (method.equalsIgnoreCase(request.getRequestLine().getMethod())) { - redirectable = true; - break; - } - return redirectable && getLocation(request, response, context) != null; - } - return false; - } - - /** - * Gets the destination of a redirection - * @return absolute URL of new location; null if not available - */ - static URL getLocation(HttpRequest request, HttpResponse response, HttpContext context) { - Header locationHdr = response.getFirstHeader("Location"); - if (locationHdr == null) { - Log.e(TAG, "Received redirection without Location header, ignoring"); - return null; - } - try { - URI location = new URI(locationHdr.getValue()); - - // some servers don't return absolute URLs as required by RFC 2616 - if (!location.isAbsolute()) { - Log.w(TAG, "Received invalid redirection to relative URL, repairing"); - URI originalURI = new URI(request.getRequestLine().getUri()); - if (!originalURI.isAbsolute()) { - final HttpHost target = HttpClientContext.adapt(context).getTargetHost(); - if (target != null) - originalURI = URIUtils.rewriteURI(originalURI, target); - else - return null; - } - return new URL(originalURI.toURL(), location.toString()); - } - return location.toURL(); - } catch (URISyntaxException e) { - Log.e(TAG, "Received redirection from/to invalid URI, ignoring", e); - } catch (MalformedURLException e) { - Log.e(TAG, "Received redirection from/to invalid URL, ignoring", e); - } - return null; - } - -} diff --git a/src/at/bitfire/davdroid/webdav/DavResponse.java b/src/at/bitfire/davdroid/webdav/DavResponse.java deleted file mode 100644 index b1ba1fd9..00000000 --- a/src/at/bitfire/davdroid/webdav/DavResponse.java +++ /dev/null @@ -1,25 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import java.util.List; - -import lombok.Getter; - -import org.simpleframework.xml.Element; -import org.simpleframework.xml.ElementList; -import org.simpleframework.xml.Root; - -@Root(strict=false) -public class DavResponse { - @Element - @Getter DavHref href; - - @ElementList(inline=true) - @Getter List propstat; -} diff --git a/src/at/bitfire/davdroid/webdav/HttpException.java b/src/at/bitfire/davdroid/webdav/HttpException.java deleted file mode 100644 index d2efd1b7..00000000 --- a/src/at/bitfire/davdroid/webdav/HttpException.java +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import lombok.Getter; - -public class HttpException extends ch.boye.httpclientandroidlib.HttpException { - private static final long serialVersionUID = -4805778240079377401L; - - @Getter private int code; - - HttpException(int code, String message) { - super(message); - this.code = code; - } - - public boolean isClientError() { - return code/100 == 4; - } - -} diff --git a/src/at/bitfire/davdroid/webdav/HttpPropfind.java b/src/at/bitfire/davdroid/webdav/HttpPropfind.java deleted file mode 100644 index 0e6e211a..00000000 --- a/src/at/bitfire/davdroid/webdav/HttpPropfind.java +++ /dev/null @@ -1,102 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import java.io.StringWriter; -import java.net.URI; -import java.util.LinkedList; - -import org.simpleframework.xml.Serializer; -import org.simpleframework.xml.core.Persister; - -import android.util.Log; -import ch.boye.httpclientandroidlib.client.methods.HttpEntityEnclosingRequestBase; -import ch.boye.httpclientandroidlib.entity.StringEntity; - -public class HttpPropfind extends HttpEntityEnclosingRequestBase { - private static final String TAG = "davdroid.HttpPropfind"; - - public final static String METHOD_NAME = "PROPFIND"; - - public enum Mode { - CURRENT_USER_PRINCIPAL, - HOME_SETS, - CARDDAV_COLLECTIONS, - CALDAV_COLLECTIONS, - COLLECTION_CTAG, - MEMBERS_ETAG - } - - - HttpPropfind(URI uri) { - setURI(uri); - } - - HttpPropfind(URI uri, Mode mode) { - this(uri); - - DavPropfind propfind = new DavPropfind(); - propfind.prop = new DavProp(); - - int depth = 0; - switch (mode) { - case CURRENT_USER_PRINCIPAL: - propfind.prop.currentUserPrincipal = new DavProp.CurrentUserPrincipal(); - break; - case HOME_SETS: - propfind.prop.addressbookHomeSet = new DavProp.AddressbookHomeSet(); - propfind.prop.calendarHomeSet = new DavProp.CalendarHomeSet(); - break; - case CARDDAV_COLLECTIONS: - depth = 1; - propfind.prop.displayname = new DavProp.DisplayName(); - propfind.prop.resourcetype = new DavProp.ResourceType(); - propfind.prop.currentUserPrivilegeSet = new LinkedList(); - propfind.prop.addressbookDescription = new DavProp.AddressbookDescription(); - propfind.prop.supportedAddressData = new LinkedList(); - break; - case CALDAV_COLLECTIONS: - depth = 1; - propfind.prop.displayname = new DavProp.DisplayName(); - propfind.prop.resourcetype = new DavProp.ResourceType(); - propfind.prop.currentUserPrivilegeSet = new LinkedList(); - propfind.prop.calendarDescription = new DavProp.CalendarDescription(); - propfind.prop.calendarColor = new DavProp.CalendarColor(); - propfind.prop.calendarTimezone = new DavProp.CalendarTimezone(); - propfind.prop.supportedCalendarComponentSet = new LinkedList(); - break; - case COLLECTION_CTAG: - propfind.prop.getctag = new DavProp.GetCTag(); - break; - case MEMBERS_ETAG: - depth = 1; - propfind.prop.getctag = new DavProp.GetCTag(); - propfind.prop.getetag = new DavProp.GetETag(); - break; - } - - try { - Serializer serializer = new Persister(); - StringWriter writer = new StringWriter(); - serializer.write(propfind, writer); - - setHeader("Content-Type", "text/xml; charset=UTF-8"); - setHeader("Accept", "text/xml"); - setHeader("Depth", String.valueOf(depth)); - setEntity(new StringEntity(writer.toString(), "UTF-8")); - } catch(Exception ex) { - Log.e(TAG, "Couldn't prepare PROPFIND request for " + uri, ex); - abort(); - } - } - - @Override - public String getMethod() { - return METHOD_NAME; - } -} diff --git a/src/at/bitfire/davdroid/webdav/HttpReport.java b/src/at/bitfire/davdroid/webdav/HttpReport.java deleted file mode 100644 index 044deba5..00000000 --- a/src/at/bitfire/davdroid/webdav/HttpReport.java +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import java.net.URI; - -import ch.boye.httpclientandroidlib.client.methods.HttpEntityEnclosingRequestBase; -import ch.boye.httpclientandroidlib.entity.StringEntity; - -public class HttpReport extends HttpEntityEnclosingRequestBase { - - public final static String METHOD_NAME = "REPORT"; - - - HttpReport(URI uri) { - setURI(uri); - } - - HttpReport(URI uri, String entity) { - this(uri); - - setHeader("Content-Type", "text/xml; charset=UTF-8"); - setHeader("Accept", "text/xml"); - setHeader("Depth", "0"); - - setEntity(new StringEntity(entity, "UTF-8")); - } - - @Override - public String getMethod() { - return METHOD_NAME; - } -} diff --git a/src/at/bitfire/davdroid/webdav/NotAuthorizedException.java b/src/at/bitfire/davdroid/webdav/NotAuthorizedException.java deleted file mode 100644 index 4eab6adb..00000000 --- a/src/at/bitfire/davdroid/webdav/NotAuthorizedException.java +++ /dev/null @@ -1,19 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import org.apache.http.HttpStatus; - -public class NotAuthorizedException extends HttpException { - private static final long serialVersionUID = 2490525047224413586L; - - public NotAuthorizedException(String reason) { - super(HttpStatus.SC_UNAUTHORIZED, reason); - } - -} diff --git a/src/at/bitfire/davdroid/webdav/NotFoundException.java b/src/at/bitfire/davdroid/webdav/NotFoundException.java deleted file mode 100644 index f612d733..00000000 --- a/src/at/bitfire/davdroid/webdav/NotFoundException.java +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import org.apache.http.HttpStatus; - -public class NotFoundException extends HttpException { - private static final long serialVersionUID = 1565961502781880483L; - - public NotFoundException(String reason) { - super(HttpStatus.SC_NOT_FOUND, reason); - } -} diff --git a/src/at/bitfire/davdroid/webdav/PreconditionFailedException.java b/src/at/bitfire/davdroid/webdav/PreconditionFailedException.java deleted file mode 100644 index 064ff38a..00000000 --- a/src/at/bitfire/davdroid/webdav/PreconditionFailedException.java +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import org.apache.http.HttpStatus; - -public class PreconditionFailedException extends HttpException { - private static final long serialVersionUID = 102282229174086113L; - - public PreconditionFailedException(String reason) { - super(HttpStatus.SC_PRECONDITION_FAILED, reason); - } -} diff --git a/src/at/bitfire/davdroid/webdav/TlsSniSocketFactory.java b/src/at/bitfire/davdroid/webdav/TlsSniSocketFactory.java deleted file mode 100644 index ce5ec2dd..00000000 --- a/src/at/bitfire/davdroid/webdav/TlsSniSocketFactory.java +++ /dev/null @@ -1,186 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; -import org.apache.commons.lang.StringUtils; -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.net.SSLCertificateSocketFactory; -import android.os.Build; -import android.util.Log; -import ch.boye.httpclientandroidlib.HttpHost; -import ch.boye.httpclientandroidlib.conn.socket.LayeredConnectionSocketFactory; -import ch.boye.httpclientandroidlib.conn.ssl.BrowserCompatHostnameVerifier; -import ch.boye.httpclientandroidlib.protocol.HttpContext; - -public class TlsSniSocketFactory implements LayeredConnectionSocketFactory { - private static final String TAG = "davdroid.SNISocketFactory"; - - final static TlsSniSocketFactory INSTANCE = new TlsSniSocketFactory(); - - private final static SSLCertificateSocketFactory sslSocketFactory = - (SSLCertificateSocketFactory)SSLCertificateSocketFactory.getDefault(0); - private final static HostnameVerifier hostnameVerifier = new BrowserCompatHostnameVerifier(); - - - /* - For SSL connections without HTTP(S) proxy: - 1) createSocket() is called - 2) connectSocket() is called which creates a new SSL connection - 2a) SNI is set up, and then - 2b) the connection is established, hands are shaken and certificate/host name are verified - - Layered sockets are used with HTTP(S) proxies: - 1) a new plain socket is created by the HTTP library - 2) the plain socket is connected to http://proxy:8080 - 3) a CONNECT request is sent to the proxy and the response is parsed - 4) now, createLayeredSocket() is called which wraps an SSL socket around the proxy connection, - doing all the set-up and verfication - 4a) Because SSLSocket.createSocket(socket, ...) always does a handshake without allowing - to set up SNI before, *** SNI is not available for layered connections *** (unless - active by Android's defaults, which it isn't at the moment). - */ - - - @Override - public Socket createSocket(HttpContext context) throws IOException { - SSLSocket ssl = (SSLSocket)sslSocketFactory.createSocket(); - setReasonableEncryption(ssl); - return ssl; - } - - @Override - public Socket connectSocket(int timeout, Socket plain, HttpHost host, InetSocketAddress remoteAddr, InetSocketAddress localAddr, HttpContext context) throws IOException { - Log.d(TAG, "Preparing direct SSL connection (without proxy) to " + host); - - // we'll rather use an SSLSocket directly - plain.close(); - - // create a plain SSL socket, but don't do hostname/certificate verification yet - SSLSocket ssl = (SSLSocket)sslSocketFactory.createSocket(remoteAddr.getAddress(), host.getPort()); - setReasonableEncryption(ssl); - - // connect, set SNI, shake hands, verify, print connection info - connectWithSNI(ssl, host.getHostName()); - - return ssl; - } - - @Override - public Socket createLayeredSocket(Socket plain, String host, int port, HttpContext context) throws IOException, UnknownHostException { - Log.d(TAG, "Preparing layered SSL connection (over proxy) to " + host); - - // create a layered SSL socket, but don't do hostname/certificate verification yet - SSLSocket ssl = (SSLSocket)sslSocketFactory.createSocket(plain, host, port, true); - setReasonableEncryption(ssl); - - // already connected, but verify host name again and print some connection info - Log.w(TAG, "Setting SNI/TLSv1.2 will silently fail because the handshake is already done"); - connectWithSNI(ssl, host); - - return ssl; - } - - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - private void connectWithSNI(SSLSocket ssl, String host) throws SSLPeerUnverifiedException { - // - set SNI host name - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - Log.d(TAG, "Using documented SNI with host name " + host); - sslSocketFactory.setHostname(ssl, host); - } else { - Log.d(TAG, "No documented SNI support on Android <4.2, trying with reflection"); - try { - java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class); - setHostnameMethod.invoke(ssl, host); - } catch (Exception e) { - Log.w(TAG, "SNI not useable", e); - } - } - - // verify hostname and certificate - SSLSession session = ssl.getSession(); - if (!hostnameVerifier.verify(host, session)) - throw new SSLPeerUnverifiedException("Cannot verify hostname: " + host); - - Log.d(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() + - " using " + session.getCipherSuite()); - } - - - @SuppressLint("DefaultLocale") - private void setReasonableEncryption(SSLSocket ssl) { - // set reasonable SSL/TLS settings before the handshake - - // Android 5.0+ (API level21) provides reasonable default settings - // but it still allows SSLv3 - // https://developer.android.com/about/versions/android-5.0-changes.html#ssl - - // - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <5.0, if available) - // - remove all SSL versions (especially SSLv3) because they're insecure now - List protocols = new LinkedList(); - for (String protocol : ssl.getSupportedProtocols()) - if (!protocol.toUpperCase().contains("SSL")) - protocols.add(protocol); - Log.v(TAG, "Setting allowed TLS protocols: " + StringUtils.join(protocols, ", ")); - ssl.setEnabledProtocols(protocols.toArray(new String[0])); - - if (android.os.Build.VERSION.SDK_INT < 21) { - // choose secure cipher suites - List allowedCiphers = Arrays.asList(new String[] { - // allowed secure ciphers according to NIST.SP.800-52r1.pdf Section 3.3.1 (see docs directory) - // TLS 1.2 - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", - "TLS_ECHDE_RSA_WITH_AES_128_GCM_SHA256", - // maximum interoperability - "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - "TLS_RSA_WITH_AES_128_CBC_SHA", - // additionally - "TLS_RSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - }); - - List availableCiphers = Arrays.asList(ssl.getSupportedCipherSuites()); - - // preferred ciphers = allowed Ciphers \ availableCiphers - HashSet preferredCiphers = new HashSet(allowedCiphers); - preferredCiphers.retainAll(availableCiphers); - - // add preferred ciphers to enabled ciphers - // for maximum security, preferred ciphers should *replace* enabled ciphers, - // but I guess for the security level of DAVdroid, disabling of insecure - // ciphers should be a server-side task - HashSet enabledCiphers = preferredCiphers; - enabledCiphers.addAll(new HashSet(Arrays.asList(ssl.getEnabledCipherSuites()))); - - Log.v(TAG, "Setting allowed TLS ciphers: " + StringUtils.join(enabledCiphers, ", ")); - ssl.setEnabledCipherSuites(enabledCiphers.toArray(new String[0])); - } - } - -} diff --git a/src/at/bitfire/davdroid/webdav/WebDavResource.java b/src/at/bitfire/davdroid/webdav/WebDavResource.java deleted file mode 100644 index 92859b7f..00000000 --- a/src/at/bitfire/davdroid/webdav/WebDavResource.java +++ /dev/null @@ -1,584 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import java.io.IOException; -import java.io.InputStream; -import java.io.StringWriter; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import lombok.Cleanup; -import lombok.Getter; -import lombok.ToString; - -import org.apache.commons.lang.StringUtils; -import org.simpleframework.xml.Serializer; -import org.simpleframework.xml.core.Persister; - -import android.util.Log; -import at.bitfire.davdroid.URLUtils; -import at.bitfire.davdroid.resource.Event; -import at.bitfire.davdroid.webdav.DavProp.Comp; -import ch.boye.httpclientandroidlib.Header; -import ch.boye.httpclientandroidlib.HttpEntity; -import ch.boye.httpclientandroidlib.HttpHost; -import ch.boye.httpclientandroidlib.HttpResponse; -import ch.boye.httpclientandroidlib.HttpStatus; -import ch.boye.httpclientandroidlib.StatusLine; -import ch.boye.httpclientandroidlib.auth.AuthScope; -import ch.boye.httpclientandroidlib.auth.UsernamePasswordCredentials; -import ch.boye.httpclientandroidlib.client.AuthCache; -import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse; -import ch.boye.httpclientandroidlib.client.methods.HttpDelete; -import ch.boye.httpclientandroidlib.client.methods.HttpGet; -import ch.boye.httpclientandroidlib.client.methods.HttpOptions; -import ch.boye.httpclientandroidlib.client.methods.HttpPut; -import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; -import ch.boye.httpclientandroidlib.entity.ByteArrayEntity; -import ch.boye.httpclientandroidlib.impl.auth.BasicScheme; -import ch.boye.httpclientandroidlib.impl.client.BasicAuthCache; -import ch.boye.httpclientandroidlib.impl.client.BasicCredentialsProvider; -import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient; -import ch.boye.httpclientandroidlib.message.BasicLineParser; -import ch.boye.httpclientandroidlib.util.EntityUtils; -import ezvcard.VCardVersion; - - -/** - * Represents a WebDAV resource (file or collection). - * This class is used for all CalDAV/CardDAV communcation. - */ -@ToString -public class WebDavResource { - private static final String TAG = "davdroid.WebDavResource"; - - public enum Property { - CURRENT_USER_PRINCIPAL, // resource detection - ADDRESSBOOK_HOMESET, CALENDAR_HOMESET, - CONTENT_TYPE, READ_ONLY, // WebDAV (common) - DISPLAY_NAME, DESCRIPTION, ETAG, - IS_COLLECTION, CTAG, // collections - IS_CALENDAR, COLOR, TIMEZONE, // CalDAV - IS_ADDRESSBOOK, VCARD_VERSION // CardDAV - } - public enum PutMode { - ADD_DONT_OVERWRITE, - UPDATE_DONT_OVERWRITE - } - - // location of this resource - @Getter protected URL location; - - // DAV capabilities (DAV: header) and allowed DAV methods (set for OPTIONS request) - protected Set capabilities = new HashSet(), - methods = new HashSet(); - - // DAV properties - protected HashMap properties = new HashMap(); - @Getter protected List supportedComponents; - - // list of members (only for collections) - @Getter protected List members; - - // content (available after GET) - @Getter protected byte[] content; - - protected CloseableHttpClient httpClient; - protected HttpClientContext context; - - - public WebDavResource(CloseableHttpClient httpClient, URL baseURL) { - this.httpClient = httpClient; - location = baseURL; - - context = HttpClientContext.create(); - context.setCredentialsProvider(new BasicCredentialsProvider()); - } - - public WebDavResource(CloseableHttpClient httpClient, URL baseURL, String username, String password, boolean preemptive) { - this(httpClient, baseURL); - - HttpHost host = new HttpHost(baseURL.getHost(), baseURL.getPort(), baseURL.getProtocol()); - context.getCredentialsProvider().setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password)); - - if (preemptive) { - Log.d(TAG, "Using preemptive authentication (not compatible with Digest auth)"); - AuthCache authCache = context.getAuthCache(); - if (authCache == null) - authCache = new BasicAuthCache(); - authCache.put(host, new BasicScheme()); - context.setAuthCache(authCache); - } - } - - WebDavResource(WebDavResource parent) { // copy constructor: based on existing WebDavResource, reuse settings - httpClient = parent.httpClient; - context = parent.context; - location = parent.location; - } - - protected WebDavResource(WebDavResource parent, URL url) { - this(parent); - location = url; - } - - public WebDavResource(WebDavResource parent, String member) throws MalformedURLException { - this(parent); - location = new URL(parent.location, URLUtils.sanitize(member)); - } - - public WebDavResource(WebDavResource parent, String member, String ETag) throws MalformedURLException { - this(parent, member); - properties.put(Property.ETAG, ETag); - } - - - - /* feature detection */ - - public void options() throws URISyntaxException, IOException, HttpException { - HttpOptions options = new HttpOptions(location.toURI()); - CloseableHttpResponse response = httpClient.execute(options, context); - try { - checkResponse(response); - - Header[] allowHeaders = response.getHeaders("Allow"); - for (Header allowHeader : allowHeaders) - methods.addAll(Arrays.asList(allowHeader.getValue().split(", ?"))); - - Header[] capHeaders = response.getHeaders("DAV"); - for (Header capHeader : capHeaders) - capabilities.addAll(Arrays.asList(capHeader.getValue().split(", ?"))); - } finally { - response.close(); - } - } - - public boolean supportsDAV(String capability) { - return capabilities.contains(capability); - } - - public boolean supportsMethod(String method) { - return methods.contains(method); - } - - - /* file hierarchy methods */ - - public String getName() { - String[] names = StringUtils.split(location.getPath(), "/"); - return names[names.length - 1]; - } - - - /* property methods */ - - public String getCurrentUserPrincipal() { - return properties.get(Property.CURRENT_USER_PRINCIPAL); - } - - public String getAddressbookHomeSet() { - return properties.get(Property.ADDRESSBOOK_HOMESET); - } - - public String getCalendarHomeSet() { - return properties.get(Property.CALENDAR_HOMESET); - } - - public String getContentType() { - return properties.get(Property.CONTENT_TYPE); - } - - public void setContentType(String mimeType) { - properties.put(Property.CONTENT_TYPE, mimeType); - } - - public boolean isReadOnly() { - return properties.containsKey(Property.READ_ONLY); - } - - public String getDisplayName() { - return properties.get(Property.DISPLAY_NAME); - } - - public String getDescription() { - return properties.get(Property.DESCRIPTION); - } - - public String getCTag() { - return properties.get(Property.CTAG); - } - public void invalidateCTag() { - properties.remove(Property.CTAG); - } - - public String getETag() { - return properties.get(Property.ETAG); - } - - public boolean isCalendar() { - return properties.containsKey(Property.IS_CALENDAR); - } - - public String getColor() { - return properties.get(Property.COLOR); - } - - public String getTimezone() { - return properties.get(Property.TIMEZONE); - } - - public boolean isAddressBook() { - return properties.containsKey(Property.IS_ADDRESSBOOK); - } - - public VCardVersion getVCardVersion() { - String versionStr = properties.get(Property.VCARD_VERSION); - return (versionStr != null) ? VCardVersion.valueOfByStr(versionStr) : null; - } - - - /* collection operations */ - - public void propfind(HttpPropfind.Mode mode) throws URISyntaxException, IOException, DavException, HttpException { - CloseableHttpResponse response = null; - - // processMultiStatus() requires knowledge of the actual content location, - // so we have to handle redirections manually and create a new request for the new location - for (int i = context.getRequestConfig().getMaxRedirects(); i > 0; i--) { - HttpPropfind propfind = new HttpPropfind(location.toURI(), mode); - response = httpClient.execute(propfind, context); - - if (response.getStatusLine().getStatusCode()/100 == 3) { - location = DavRedirectStrategy.getLocation(propfind, response, context); - Log.i(TAG, "Redirection on PROPFIND; trying again at new content URL: " + location); - // don't forget to throw away the unneeded response content - HttpEntity entity = response.getEntity(); - if (entity != null) { @Cleanup InputStream content = entity.getContent(); } - } else - break; // answer was NOT a redirection, continue - } - if (response == null) - throw new DavNoContentException(); - - try { - checkResponse(response); // will also handle Content-Location - processMultiStatus(response); - } finally { - response.close(); - } - } - - public void multiGet(DavMultiget.Type type, String[] names) throws URISyntaxException, IOException, DavException, HttpException { - CloseableHttpResponse response = null; - - // processMultiStatus() requires knowledge of the actual content location, - // so we have to handle redirections manually and create a new request for the new location - for (int i = context.getRequestConfig().getMaxRedirects(); i > 0; i--) { - // build multi-get XML request - List hrefs = new LinkedList(); - for (String name : names) - hrefs.add(new URL(location, name).getPath()); - DavMultiget multiget = DavMultiget.newRequest(type, hrefs.toArray(new String[0])); - - StringWriter writer = new StringWriter(); - try { - Serializer serializer = new Persister(); - serializer.write(multiget, writer); - } catch (Exception ex) { - Log.e(TAG, "Couldn't create XML multi-get request", ex); - throw new DavException("Couldn't create multi-get request"); - } - - // submit REPORT request - HttpReport report = new HttpReport(location.toURI(), writer.toString()); - response = httpClient.execute(report, context); - - if (response.getStatusLine().getStatusCode()/100 == 3) { - location = DavRedirectStrategy.getLocation(report, response, context); - Log.i(TAG, "Redirection on REPORT multi-get; trying again at new content URL: " + location); - - // don't forget to throw away the unneeded response content - HttpEntity entity = response.getEntity(); - if (entity != null) { @Cleanup InputStream content = entity.getContent(); } - } else - break; // answer was NOT a redirection, continue - } - if (response == null) - throw new DavNoContentException(); - - try { - checkResponse(response); // will also handle Content-Location - processMultiStatus(response); - } finally { - response.close(); - } - } - - - /* resource operations */ - - public void get(String acceptedType) throws URISyntaxException, IOException, HttpException, DavException { - HttpGet get = new HttpGet(location.toURI()); - get.addHeader("Accept", acceptedType); - - CloseableHttpResponse response = httpClient.execute(get, context); - try { - checkResponse(response); - - HttpEntity entity = response.getEntity(); - if (entity == null) - throw new DavNoContentException(); - - content = EntityUtils.toByteArray(entity); - } finally { - response.close(); - } - } - - // returns the ETag of the created/updated resource, if available (null otherwise) - public String put(byte[] data, PutMode mode) throws URISyntaxException, IOException, HttpException { - HttpPut put = new HttpPut(location.toURI()); - put.setEntity(new ByteArrayEntity(data)); - - switch (mode) { - case ADD_DONT_OVERWRITE: - put.addHeader("If-None-Match", "*"); - break; - case UPDATE_DONT_OVERWRITE: - put.addHeader("If-Match", (getETag() != null) ? getETag() : "*"); - break; - } - - if (getContentType() != null) - put.addHeader("Content-Type", getContentType()); - - CloseableHttpResponse response = httpClient.execute(put, context); - try { - checkResponse(response); - - Header eTag = response.getLastHeader("ETag"); - if (eTag != null) - return eTag.getValue(); - } finally { - response.close(); - } - - return null; - } - - public void delete() throws URISyntaxException, IOException, HttpException { - HttpDelete delete = new HttpDelete(location.toURI()); - - if (getETag() != null) - delete.addHeader("If-Match", getETag()); - - CloseableHttpResponse response = httpClient.execute(delete, context); - try { - checkResponse(response); - } finally { - response.close(); - } - } - - - /* helpers */ - - protected void checkResponse(HttpResponse response) throws HttpException { - checkResponse(response.getStatusLine()); - - // handle Content-Location header (see RFC 4918 5.2 Collection Resources) - Header contentLocationHdr = response.getFirstHeader("Content-Location"); - if (contentLocationHdr != null) - try { - // Content-Location was set, update location correspondingly - location = new URL(location, contentLocationHdr.getValue()); - Log.d(TAG, "Set Content-Location to " + location); - } catch (MalformedURLException e) { - Log.w(TAG, "Ignoring invalid Content-Location", e); - } - } - - protected static void checkResponse(StatusLine statusLine) throws HttpException { - int code = statusLine.getStatusCode(); - - if (code/100 == 1 || code/100 == 2) // everything OK - return; - - String reason = code + " " + statusLine.getReasonPhrase(); - switch (code) { - case HttpStatus.SC_UNAUTHORIZED: - throw new NotAuthorizedException(reason); - case HttpStatus.SC_NOT_FOUND: - throw new NotFoundException(reason); - case HttpStatus.SC_PRECONDITION_FAILED: - throw new PreconditionFailedException(reason); - default: - throw new HttpException(code, reason); - } - } - - protected void processMultiStatus(HttpResponse response) throws IOException, HttpException, DavException { - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_MULTI_STATUS) - throw new DavNoMultiStatusException(); - - HttpEntity entity = response.getEntity(); - if (entity == null) - throw new DavNoContentException(); - @Cleanup InputStream content = entity.getContent(); - - DavMultistatus multiStatus; - try { - Serializer serializer = new Persister(); - multiStatus = serializer.read(DavMultistatus.class, content, false); - } catch (Exception ex) { - throw new DavException("Couldn't parse Multi-Status response on REPORT multi-get", ex); - } - - if (multiStatus.response == null) // empty response - throw new DavNoContentException(); - - // member list will be built from response - List members = new LinkedList(); - - // iterate through all resources (either ourselves or member) - for (DavResponse singleResponse : multiStatus.response) { - URL href; - try { - href = new URL(location, URLUtils.sanitize(singleResponse.getHref().href)); - } catch(IllegalArgumentException ex) { - Log.w(TAG, "Ignoring illegal member URI in multi-status response", ex); - continue; - } - Log.d(TAG, "Processing multi-status element: " + href); - - // process known properties - HashMap properties = new HashMap(); - List supportedComponents = null; - byte[] data = null; - - for (DavPropstat singlePropstat : singleResponse.getPropstat()) { - StatusLine status = BasicLineParser.parseStatusLine(singlePropstat.status, new BasicLineParser()); - - // ignore information about missing properties etc. - if (status.getStatusCode()/100 != 1 && status.getStatusCode()/100 != 2) - continue; - DavProp prop = singlePropstat.prop; - - if (prop.currentUserPrincipal != null && prop.currentUserPrincipal.getHref() != null) - properties.put(Property.CURRENT_USER_PRINCIPAL, prop.currentUserPrincipal.getHref().href); - - if (prop.currentUserPrivilegeSet != null) { - // privilege info available - boolean mayAll = false, - mayBind = false, - mayUnbind = false, - mayWrite = false, - mayWriteContent = false; - for (DavProp.Privilege privilege : prop.currentUserPrivilegeSet) { - if (privilege.getAll() != null) mayAll = true; - if (privilege.getBind() != null) mayBind = true; - if (privilege.getUnbind() != null) mayUnbind = true; - if (privilege.getWrite() != null) mayWrite = true; - if (privilege.getWriteContent() != null) mayWriteContent = true; - } - if (!mayAll && !mayWrite && !(mayWriteContent && mayBind && mayUnbind)) - properties.put(Property.READ_ONLY, "1"); - } - - if (prop.addressbookHomeSet != null && prop.addressbookHomeSet.getHref() != null) - properties.put(Property.ADDRESSBOOK_HOMESET, URLUtils.ensureTrailingSlash(prop.addressbookHomeSet.getHref().href)); - - if (prop.calendarHomeSet != null && prop.calendarHomeSet.getHref() != null) - properties.put(Property.CALENDAR_HOMESET, URLUtils.ensureTrailingSlash(prop.calendarHomeSet.getHref().href)); - - if (prop.displayname != null) - properties.put(Property.DISPLAY_NAME, prop.displayname.getDisplayName()); - - if (prop.resourcetype != null) { - if (prop.resourcetype.getCollection() != null) { - properties.put(Property.IS_COLLECTION, "1"); - // is a collection, ensure trailing slash - href = URLUtils.ensureTrailingSlash(href); - } - if (prop.resourcetype.getAddressbook() != null) { // CardDAV collection properties - properties.put(Property.IS_ADDRESSBOOK, "1"); - - if (prop.addressbookDescription != null) - properties.put(Property.DESCRIPTION, prop.addressbookDescription.getDescription()); - if (prop.supportedAddressData != null) - for (DavProp.AddressDataType dataType : prop.supportedAddressData) - if ("text/vcard".equalsIgnoreCase(dataType.getContentType())) - // ignore "3.0" as it MUST be supported anyway - if ("4.0".equals(dataType.getVersion())) - properties.put(Property.VCARD_VERSION, VCardVersion.V4_0.getVersion()); - } - if (prop.resourcetype.getCalendar() != null) { // CalDAV collection propertioes - properties.put(Property.IS_CALENDAR, "1"); - - if (prop.calendarDescription != null) - properties.put(Property.DESCRIPTION, prop.calendarDescription.getDescription()); - - if (prop.calendarColor != null) - properties.put(Property.COLOR, prop.calendarColor.getColor()); - - if (prop.calendarTimezone != null) - try { - properties.put(Property.TIMEZONE, Event.TimezoneDefToTzId(prop.calendarTimezone.getTimezone())); - } catch(IllegalArgumentException e) { - } - - if (prop.supportedCalendarComponentSet != null) { - supportedComponents = new LinkedList(); - for (Comp component : prop.supportedCalendarComponentSet) - supportedComponents.add(component.getName()); - } - } - } - - if (prop.getctag != null) - properties.put(Property.CTAG, prop.getctag.getCTag()); - - if (prop.getetag != null) - properties.put(Property.ETAG, prop.getetag.getETag()); - - if (prop.calendarData != null && prop.calendarData.ical != null) - data = prop.calendarData.ical.getBytes(); - else if (prop.addressData != null && prop.addressData.vcard != null) - data = prop.addressData.vcard.getBytes(); - } - - // about which resource is this response? - if (location.equals(href) || URLUtils.ensureTrailingSlash(location).equals(href)) { // about ourselves - this.properties.putAll(properties); - if (supportedComponents != null) - this.supportedComponents = supportedComponents; - this.content = data; - - } else { // about a member - WebDavResource member = new WebDavResource(this, href); - member.properties = properties; - member.supportedComponents = supportedComponents; - member.content = data; - - members.add(member); - } - } - - this.members = members; - } - -} diff --git a/src/ical4j.properties b/src/ical4j.properties deleted file mode 100644 index 6db152a4..00000000 --- a/src/ical4j.properties +++ /dev/null @@ -1,6 +0,0 @@ - -net.fortuna.ical4j.timezone.update.enabled=false - -ical4j.unfolding.relaxed=true -ical4j.parsing.relaxed=true -ical4j.compatibility.outlook=true diff --git a/test/AndroidManifest.xml b/test/AndroidManifest.xml deleted file mode 100644 index d63a92b4..00000000 --- a/test/AndroidManifest.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/test/assets/all-day-0sec.ics b/test/assets/all-day-0sec.ics deleted file mode 100644 index 07679f29..00000000 --- a/test/assets/all-day-0sec.ics +++ /dev/null @@ -1,11 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//hacksw/handcal//NONSGML v1.0//EN -BEGIN:VEVENT -UID:all-day-0sec@example.com -DTSTAMP:20140101T000000Z -DTSTART;VALUE=DATE:19970714 -DTEND;VALUE=DATE:19970714 -SUMMARY:0 Sec Event -END:VEVENT -END:VCALENDAR \ No newline at end of file diff --git a/test/assets/all-day-10days.ics b/test/assets/all-day-10days.ics deleted file mode 100644 index 52e6dbdf..00000000 --- a/test/assets/all-day-10days.ics +++ /dev/null @@ -1,11 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//hacksw/handcal//NONSGML v1.0//EN -BEGIN:VEVENT -UID:all-day-10days@example.com -DTSTAMP:20140101T000000Z -DTSTART;VALUE=DATE:19970714 -DTEND;VALUE=DATE:19970724 -SUMMARY:All-Day 10 Days -END:VEVENT -END:VCALENDAR \ No newline at end of file diff --git a/test/assets/all-day-1day.ics b/test/assets/all-day-1day.ics deleted file mode 100644 index ead04361..00000000 --- a/test/assets/all-day-1day.ics +++ /dev/null @@ -1,11 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//hacksw/handcal//NONSGML v1.0//EN -BEGIN:VEVENT -UID:all-day-1day@example.com -DTSTAMP:20140101T000000Z -DTSTART;VALUE=DATE:19970714 -DTEND;VALUE=DATE:19970714 -SUMMARY:All-Day 1 Day -END:VEVENT -END:VCALENDAR \ No newline at end of file diff --git a/test/assets/event-on-that-day.ics b/test/assets/event-on-that-day.ics deleted file mode 100644 index 0ccbd4f3..00000000 --- a/test/assets/event-on-that-day.ics +++ /dev/null @@ -1,11 +0,0 @@ -BEGIN:VCALENDAR -VERSION:2.0 -PRODID:-//hacksw/handcal//NONSGML v1.0//EN -BEGIN:VEVENT -UID:event-on-that-day@example.com -DTSTAMP:19970714T170000Z -ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com -DTSTART;VALUE=DATE:19970714 -SUMMARY:Bastille Day Party -END:VEVENT -END:VCALENDAR \ No newline at end of file diff --git a/test/assets/impp.vcf b/test/assets/impp.vcf deleted file mode 100644 index c08f9029..00000000 --- a/test/assets/impp.vcf +++ /dev/null @@ -1,9 +0,0 @@ -BEGIN:VCARD -VERSION:3.0 -UID:2de59c6cc9 -PRODID:-//ownCloud//NONSGML Contacts 0.2.5//EN -REV:2013-12-08T00:04:30+00:00 -FN:test mctest -N:mctest;test;;; -IMPP;TYPE=WORK;X-SERVICE-TYPE=jabber:test-without-valid-scheme@test.tld -END:VCARD diff --git a/test/assets/invalid-unknown-properties.vcf b/test/assets/invalid-unknown-properties.vcf deleted file mode 100644 index 3fd77fce..00000000 --- a/test/assets/invalid-unknown-properties.vcf +++ /dev/null @@ -1,5 +0,0 @@ -BEGIN:VCARD -VERSION:3.0 -FN:VCard with invalid unknown properties -X-UNKNOWN@PROPERTY:MUST-NOT_CONTAIN?OTHER*LETTERS; -END:VCARD \ No newline at end of file diff --git a/test/assets/reference.vcf b/test/assets/reference.vcf deleted file mode 100644 index ba6a6b4d..00000000 --- a/test/assets/reference.vcf +++ /dev/null @@ -1,16 +0,0 @@ -BEGIN:VCARD -VERSION:3.0 -N:Gump;Forrest;Mr. -FN:Forrest Gump -ORG:Bubba Gump Shrimp Co. -TITLE:Shrimp Man -PHOTO;VALUE=URL;TYPE=GIF:http://www.example.com/dir_photos/my_photo.gif -TEL;TYPE=WORK,VOICE:(111) 555-1212 -TEL;TYPE=HOME,VOICE:(404) 555-1212 -ADR;TYPE=WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America -LABEL;TYPE=WORK:100 Waters Edge\nBaytown, LA 30314\nUnited States of America -ADR;TYPE=HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America -LABEL;TYPE=HOME:42 Plantation St.\nBaytown, LA 30314\nUnited States of America -EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com -REV:2008-04-24T19:52:43Z -END:VCARD \ No newline at end of file diff --git a/test/assets/test.random b/test/assets/test.random deleted file mode 100644 index eb3e5b02..00000000 Binary files a/test/assets/test.random and /dev/null differ diff --git a/test/assets/vcard3-sample1.vcf b/test/assets/vcard3-sample1.vcf deleted file mode 100644 index f5c9f977..00000000 --- a/test/assets/vcard3-sample1.vcf +++ /dev/null @@ -1,16 +0,0 @@ -BEGIN:VCARD -VERSION:3.0 -N:Gump;Forrest -FN:Forrest Gump -ORG:Bubba Gump Shrimp Co. -TITLE:Shrimp Man -PHOTO;VALUE=URL;TYPE=GIF:http://www.example.com/dir_photos/my_photo.gif -TEL;TYPE=WORK,VOICE:(111) 555-1212 -TEL;TYPE=HOME,VOICE:(404) 555-1212 -ADR;TYPE=WORK:;;100 Waters Edge;Baytown;LA;30314;United States of America -LABEL;TYPE=WORK:100 Waters Edge\nBaytown, LA 30314\nUnited States of America -ADR;TYPE=HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America -LABEL;TYPE=HOME:42 Plantation St.\nBaytown, LA 30314\nUnited States of America -EMAIL;TYPE=PREF,INTERNET:forrestgump@example.com -REV:2008-04-24T19:52:43Z -END:VCARD \ No newline at end of file diff --git a/test/assets/vienna-evolution.ics b/test/assets/vienna-evolution.ics deleted file mode 100644 index 5f11911a..00000000 --- a/test/assets/vienna-evolution.ics +++ /dev/null @@ -1,33 +0,0 @@ -BEGIN:VCALENDAR -PRODID:-//Ximian//NONSGML Evolution Calendar//EN -VERSION:2.0 -METHOD:PUBLISH -BEGIN:VTIMEZONE -TZID:/freeassociation.sourceforge.net/Tzfile/Europe/Vienna -X-LIC-LOCATION:Europe/Vienna -BEGIN:STANDARD -TZNAME:CET -DTSTART:19701027T030000 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 -TZOFFSETFROM:+0200 -TZOFFSETTO:+0100 -END:STANDARD -BEGIN:DAYLIGHT -TZNAME:CEST -DTSTART:19700331T020000 -RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 -TZOFFSETFROM:+0100 -TZOFFSETTO:+0200 -END:DAYLIGHT -END:VTIMEZONE -BEGIN:VEVENT -UID:c252087c-7354-4722-aea9-0e7d86c01a25 -DTSTAMP:20130926T151211Z -SUMMARY:Test-Ereignis im schönen Wien -DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Vienna:20131009T170000 -DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Vienna:20131009T180000 -X-RADICALE-NAME:97929342-291a-434e-bf1a-fa1749bf99d0.ics -X-EVOLUTION-CALDAV-HREF:/radicale/rfc2822/default.ics/97929342-291a-434e-bf1a-fa1749bf99d0.ics -X-EVOLUTION-CALDAV-ETAG:\"-3264224243575339985\" -END:VEVENT -END:VCALENDAR diff --git a/test/proguard-project.txt b/test/proguard-project.txt deleted file mode 100644 index f2fe1559..00000000 --- a/test/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/test/project.properties b/test/project.properties deleted file mode 100644 index 4ab12569..00000000 --- a/test/project.properties +++ /dev/null @@ -1,14 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-19 diff --git a/test/res/drawable-hdpi/ic_launcher.png b/test/res/drawable-hdpi/ic_launcher.png deleted file mode 100644 index 96a442e5..00000000 Binary files a/test/res/drawable-hdpi/ic_launcher.png and /dev/null differ diff --git a/test/res/drawable-ldpi/ic_launcher.png b/test/res/drawable-ldpi/ic_launcher.png deleted file mode 100644 index 99238729..00000000 Binary files a/test/res/drawable-ldpi/ic_launcher.png and /dev/null differ diff --git a/test/res/drawable-mdpi/ic_launcher.png b/test/res/drawable-mdpi/ic_launcher.png deleted file mode 100644 index 359047df..00000000 Binary files a/test/res/drawable-mdpi/ic_launcher.png and /dev/null differ diff --git a/test/res/drawable-xhdpi/ic_launcher.png b/test/res/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index 71c6d760..00000000 Binary files a/test/res/drawable-xhdpi/ic_launcher.png and /dev/null differ diff --git a/test/res/values/strings.xml b/test/res/values/strings.xml deleted file mode 100644 index 2539643a..00000000 --- a/test/res/values/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - DavdroidTest - - diff --git a/test/robohydra/.gitignore b/test/robohydra/.gitignore deleted file mode 100644 index 3c3629e6..00000000 --- a/test/robohydra/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/test/robohydra/davdroid.conf b/test/robohydra/davdroid.conf deleted file mode 100644 index 37807fd7..00000000 --- a/test/robohydra/davdroid.conf +++ /dev/null @@ -1,6 +0,0 @@ -{"plugins":[ - "assets", - "redirect", - "dav", - "dav-invalid" -]} diff --git a/test/robohydra/plugins/assets/index.js b/test/robohydra/plugins/assets/index.js deleted file mode 100644 index 2a254b9a..00000000 --- a/test/robohydra/plugins/assets/index.js +++ /dev/null @@ -1,12 +0,0 @@ -var RoboHydraHeadFilesystem = require("robohydra").heads.RoboHydraHeadFilesystem; - -exports.getBodyParts = function(conf) { - return { - heads: [ - new RoboHydraHeadFilesystem({ - mountPath: '/assets/', - documentRoot: '../assets' - }) - ] - }; -}; diff --git a/test/robohydra/plugins/dav-invalid/index.js b/test/robohydra/plugins/dav-invalid/index.js deleted file mode 100644 index b14ee944..00000000 --- a/test/robohydra/plugins/dav-invalid/index.js +++ /dev/null @@ -1,51 +0,0 @@ -var roboHydraHeadDAV = require("../headdav"); - -exports.getBodyParts = function(conf) { - return { - heads: [ - /* address-book home set */ - new RoboHydraHeadDAV({ - path: "/dav-invalid/addressbooks/user%40domain/", - handler: function(req,res,next) { - if (req.method == "PROPFIND" && req.rawBody.toString().match(/addressbook-description/)) { - res.statusCode = 207; - res.write('\\ - \ - \ - /dav/addressbooks/user@domain/My Contacts:1.vcf/\ - \ - \ - \ - \ - \ - \ - \ - Address Book with dubious characters in path\ - \ - \ - HTTP/1.1 200 OK\ - \ - \ - \ - HTTPS://example.com/user@domain/absolute-url.vcf\ - \ - \ - \ - \ - \ - \ - \ - Address Book with absolute URL and at sign in path\ - \ - \ - HTTP/1.1 200 OK\ - \ - \ - \ - '); - } - } - }) - ] - }; -}; diff --git a/test/robohydra/plugins/dav/index.js b/test/robohydra/plugins/dav/index.js deleted file mode 100644 index bdf7cee3..00000000 --- a/test/robohydra/plugins/dav/index.js +++ /dev/null @@ -1,269 +0,0 @@ -var roboHydraHeadDAV = require("../headdav"); - -exports.getBodyParts = function(conf) { - return { - heads: [ - /* base URL, provide default DAV here */ - new RoboHydraHeadDAV({ path: "/dav/" }), - - /* multistatus parsing */ - new RoboHydraHeadDAV({ - path: "/dav/collection-response-with-trailing-slash", - handler: function(req,res,next) { - if (req.method == "PROPFIND") { - res.statusCode = 207; - res.write('\\ - \ - \ - /dav/collection-response-with-trailing-slash/ \ - \ - \ - \ - /principals/ok\ - \ - \ - \ - \ - \ - HTTP/1.1 200 OK\ - \ - \ - \ - '); - } - } - }), - new RoboHydraHeadDAV({ - path: "/dav/collection-response-without-trailing-slash", - handler: function(req,res,next) { - if (req.method == "PROPFIND") { - res.statusCode = 207; - res.write('\\ - \ - \ - /dav/collection-response-without-trailing-slash \ - \ - \ - \ - /principals/ok\ - \ - \ - \ - \ - \ - HTTP/1.1 200 OK\ - \ - \ - \ - '); - } - } - }), - - /* principal URL */ - new RoboHydraHeadDAV({ - path: "/dav/principals/users/test", - handler: function(req,res,next) { - if (req.method == "PROPFIND" && req.rawBody.toString().match(/home-?set/)) { - res.statusCode = 207; - res.write('\\ - \ - \ - ' + req.url + ' \ - \ - \ - \ - /dav/addressbooks/test\ - \ - \ - /dav/calendars/test/\ - \ - \ - HTTP/1.1 200 OK\ - \ - \ - \ - '); - } - } - }), - - /* address-book home set */ - new RoboHydraHeadDAV({ - path: "/dav/addressbooks/test/", - handler: function(req,res,next) { - if (!req.url.match(/\/$/)) { - res.statusCode = 302; - res.headers['location'] = "/dav/addressbooks/test/"; - } - else if (req.method == "PROPFIND" && req.rawBody.toString().match(/addressbook-description/)) { - res.statusCode = 207; - res.write('\\ - \ - \ - /dav/addressbooks/test/useless-member\ - \ - \ - \ - \ - HTTP/1.1 200 OK\ - \ - \ - \ - /dav/addressbooks/test/default-v4.vcf/\ - \ - \ - \ - \ - \ - \ - Default Address Book\ - \ - \ - \ - \ - \ - HTTP/1.1 200 OK\ - \ - \ - \ - '); - } - } - }), - - /* calendar home set */ - new RoboHydraHeadDAV({ - path: "/dav/calendars/test/", - handler: function(req,res,next) { - if (req.method == "PROPFIND" && req.rawBody.toString().match(/calendar-description/)) { - res.statusCode = 207; - res.write('\\ - \ - \ - /dav/calendars/test/shared.forbidden\ - \ - \ - \ - \ - HTTP/1.1 403 Forbidden\ - \ - \ - \ - /dav/calendars/test/private.ics\ - \ - \ - \ - \ - \ - \ - Private Calendar\ - This is my private calendar.\ - \ - HTTP/1.1 200 OK\ - \ - \ - \ - /dav/calendars/test/work.ics\ - \ - \ - \ - \ - \ - \ - \ - \ - \ - Work Calendar\ - 0xFF00FF\ - \ - HTTP/1.1 200 OK\ - \ - \ - \ - '); - } - } - }), - - /* non-existing file */ - new RoboHydraHeadDAV({ - path: "/dav/collection/new.file", - handler: function(req,res,next) { - if (req.method == "PUT") { - if (req.headers['if-match']) /* can't overwrite new file */ - res.statusCode = 412; - else { - res.statusCode = 201; - res.headers["ETag"] = "has-just-been-created"; - } - - } else if (req.method == "DELETE") - res.statusCode = 404; - } - }), - - /* existing file */ - new RoboHydraHeadDAV({ - path: "/dav/collection/existing.file", - handler: function(req,res,next) { - if (req.method == "PUT") { - if (req.headers['if-none-match']) /* requested "don't overwrite", but this file exists */ - res.statusCode = 412; - else { - res.statusCode = 204; - res.headers["ETag"] = "has-just-been-updated"; - } - - } else if (req.method == "DELETE") - res.statusCode = 204; - } - }), - - /* address-book multiget */ - new RoboHydraHeadDAV({ - path: "/dav/addressbooks/default.vcf/", - handler: function(req,res,next) { - if (req.method == "REPORT" && req.rawBody.toString().match(/addressbook-multiget[\s\S]+[\s\S]+/m)) { - res.statusCode = 207; - res.write('\\ - \ - \ - /dav/addressbooks/default.vcf/1.vcf\ - \ - \ - \ - BEGIN:VCARD\ - VERSION:3.0\ - NICKNAME:MULTIGET1\ - UID:1\ - END:VCARD\ - \ - \ - HTTP/1.1 200 OK\ - \ - \ - \ - /dav/addressbooks/default.vcf/2.vcf\ - \ - \ - \ - BEGIN:VCARD\ - VERSION:3.0\ - NICKNAME:MULTIGET2\ - UID:2\ - END:VCARD\ - \ - \ - HTTP/1.1 200 OK\ - \ - \ - \ - '); - } - } - }), - - ] - }; -}; diff --git a/test/robohydra/plugins/headdav.js b/test/robohydra/plugins/headdav.js deleted file mode 100644 index 613a2678..00000000 --- a/test/robohydra/plugins/headdav.js +++ /dev/null @@ -1,57 +0,0 @@ -var roboHydra = require("robohydra"), - roboHydraHeads = roboHydra.heads, - roboHydraHead = roboHydraHeads.RoboHydraHead; - -RoboHydraHeadDAV = roboHydraHeads.roboHydraHeadType({ - name: 'WebDAV Server', - mandatoryProperties: [ 'path' ], - optionalProperties: [ 'handler' ], - - parentPropBuilder: function() { - var myHandler = this.handler; - return { - path: this.path, - handler: function(req,res,next) { - // default DAV behavior - res.headers['DAV'] = 'addressbook, calendar-access'; - res.statusCode = 500; - - // verify Accept header - var accept = req.headers['accept']; - if (req.method == "GET" && (accept == undefined || !accept.match(/text\/(calendar|vcard|xml)/)) || - (req.method == "PROPFIND" || req.method == "REPORT") && (accept == undefined || accept != "text/xml")) - res.statusCode = 406; - - // DAV operations that work on all URLs - else if (req.method == "OPTIONS") { - res.statusCode = 204; - res.headers['Allow'] = 'OPTIONS, PROPFIND, GET, PUT, DELETE, REPORT'; - - } else if (req.method == "PROPFIND" && req.rawBody.toString().match(/current-user-principal/)) { - res.statusCode = 207; - res.write('\\ - \ - \ - ' + req.url + ' \ - \ - \ - \ - /dav/principals/users/test\ - \ - \ - HTTP/1.1 200 OK\ - \ - \ - \ - '); - - } else if (typeof myHandler != 'undefined') - myHandler(req,res,next); - - res.end(); - } - } - } -}); - -module.exports = RoboHydraHeadDAV; diff --git a/test/robohydra/plugins/redirect/index.js b/test/robohydra/plugins/redirect/index.js deleted file mode 100644 index 4c2cbab0..00000000 --- a/test/robohydra/plugins/redirect/index.js +++ /dev/null @@ -1,57 +0,0 @@ -require('../simple'); - -var RoboHydraHead = require('robohydra').heads.RoboHydraHead; - -exports.getBodyParts = function(conf) { - return { - heads: [ - // well-known URIs - new SimpleResponseHead({ - path: '/.well-known/caldav', - status: 302, - headers: { Location: '/dav/' } - }), - new SimpleResponseHead({ - path: '/.well-known/carddav', - status: 302, - headers: { Location: '/dav/' } - }), - - // generic redirections - new RoboHydraHead({ - path: '/redirect/301', - handler: function(req,res,next) { - res.statusCode = 301; - var location = req.queryParams['to'] || '/assets/test.random'; - res.headers = { - Location: location - } - res.end(); - } - }), - new RoboHydraHead({ - path: '/redirect/302', - handler: function(req,res,next) { - res.statusCode = 302; - var location = req.queryParams['to'] || '/assets/test.random'; - res.headers = { - Location: location - } - res.end(); - } - }), - - // special redirections - new SimpleResponseHead({ - path: '/redirect/relative', - status: 302, - headers: { Location: '/new/location' } - }), - new SimpleResponseHead({ - path: '/redirect/without-location', - status: 302 - }) - - ] - }; -}; diff --git a/test/robohydra/plugins/simple.js b/test/robohydra/plugins/simple.js deleted file mode 100644 index 3d506ee2..00000000 --- a/test/robohydra/plugins/simple.js +++ /dev/null @@ -1,28 +0,0 @@ -var roboHydra = require("robohydra"), - roboHydraHeads = roboHydra.heads, - roboHydraHead = roboHydraHeads.RoboHydraHead; - -SimpleResponseHead = roboHydraHeads.roboHydraHeadType({ - name: 'Simple HTTP Response', - mandatoryProperties: [ 'path', 'status' ], - optionalProperties: [ 'headers', 'body' ], - - parentPropBuilder: function() { - var head = this; - return { - path: this.path, - handler: function(req,res,next) { - res.statusCode = head.status; - if (typeof head.headers != 'undefined') - res.headers = head.headers; - if (typeof head.body != 'undefined') - res.write(head.body); - else - res.write(); - res.end(); - } - } - } -}); - -module.exports = SimpleResponseHead; diff --git a/test/robohydra/run.sh b/test/robohydra/run.sh deleted file mode 100755 index f53c7967..00000000 --- a/test/robohydra/run.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -node_modules/robohydra/bin/robohydra.js davdroid.conf -I plugins diff --git a/test/src/at/bitfire/davdroid/resource/test/ContactTest.java b/test/src/at/bitfire/davdroid/resource/test/ContactTest.java deleted file mode 100644 index 26ee6b1b..00000000 --- a/test/src/at/bitfire/davdroid/resource/test/ContactTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource.test; - -import java.io.IOException; -import java.io.InputStream; - -import ezvcard.property.Email; -import ezvcard.property.Telephone; -import lombok.Cleanup; -import android.content.res.AssetManager; -import android.test.InstrumentationTestCase; -import at.bitfire.davdroid.resource.Contact; -import at.bitfire.davdroid.resource.InvalidResourceException; - -public class ContactTest extends InstrumentationTestCase { - AssetManager assetMgr; - - public void setUp() throws IOException, InvalidResourceException { - assetMgr = getInstrumentation().getContext().getResources().getAssets(); - } - - public void testReferenceVCard() throws IOException, InvalidResourceException { - Contact c = parseVCF("reference.vcf"); - assertEquals("Gump", c.getFamilyName()); - assertEquals("Forrest", c.getGivenName()); - assertEquals("Forrest Gump", c.getDisplayName()); - assertEquals("Bubba Gump Shrimp Co.", c.getOrganization().getValues().get(0)); - assertEquals("Shrimp Man", c.getJobTitle()); - - Telephone phone1 = c.getPhoneNumbers().get(0); - assertEquals("(111) 555-1212", phone1.getText()); - assertEquals("WORK", phone1.getParameters("TYPE").get(0)); - assertEquals("VOICE", phone1.getParameters("TYPE").get(1)); - - Telephone phone2 = c.getPhoneNumbers().get(1); - assertEquals("(404) 555-1212", phone2.getText()); - assertEquals("HOME", phone2.getParameters("TYPE").get(0)); - assertEquals("VOICE", phone2.getParameters("TYPE").get(1)); - - Email email = c.getEmails().get(0); - assertEquals("forrestgump@example.com", email.getValue()); - assertEquals("PREF", email.getParameters("TYPE").get(0)); - assertEquals("INTERNET", email.getParameters("TYPE").get(1)); - } - - public void testParseInvalidUnknownProperties() throws IOException, InvalidResourceException { - Contact c = parseVCF("invalid-unknown-properties.vcf"); - assertEquals("VCard with invalid unknown properties", c.getDisplayName()); - assertNull(c.getUnknownProperties()); - } - - - protected Contact parseVCF(String fname) throws IOException, InvalidResourceException { - @Cleanup InputStream in = assetMgr.open(fname, AssetManager.ACCESS_STREAMING); - Contact c = new Contact(fname, null); - c.parseEntity(in); - return c; - } -} diff --git a/test/src/at/bitfire/davdroid/resource/test/EventTest.java b/test/src/at/bitfire/davdroid/resource/test/EventTest.java deleted file mode 100644 index 89afdc1c..00000000 --- a/test/src/at/bitfire/davdroid/resource/test/EventTest.java +++ /dev/null @@ -1,127 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource.test; - -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; - - Event eViennaEvolution, - eOnThatDay, eAllDay1Day, eAllDay10Days, eAllDay0Sec; - - public void setUp() throws IOException, InvalidResourceException { - assetMgr = getInstrumentation().getContext().getResources().getAssets(); - - eViennaEvolution = parseCalendar("vienna-evolution.ics"); - eOnThatDay = parseCalendar("event-on-that-day.ics"); - eAllDay1Day = parseCalendar("all-day-1day.ics"); - eAllDay10Days = parseCalendar("all-day-10days.ics"); - eAllDay0Sec = parseCalendar("all-day-0sec.ics"); - - //assertEquals("Test-Ereignis im schönen Wien", e.getSummary()); - } - - - public void testStartEndTimes() throws IOException, ParserException { - // event with start+end date-time - assertEquals(1381330800000L, eViennaEvolution.getDtStartInMillis()); - assertEquals("Europe/Vienna", eViennaEvolution.getDtStartTzID()); - assertEquals(1381334400000L, eViennaEvolution.getDtEndInMillis()); - assertEquals("Europe/Vienna", eViennaEvolution.getDtEndTzID()); - } - - public void testStartEndTimesAllDay() throws IOException, ParserException { - // event with start date only - assertEquals(868838400000L, eOnThatDay.getDtStartInMillis()); - assertEquals(Time.TIMEZONE_UTC, eOnThatDay.getDtStartTzID()); - // DTEND missing in VEVENT, must have been set to DTSTART+1 day - assertEquals(868838400000L + 86400000, eOnThatDay.getDtEndInMillis()); - assertEquals(Time.TIMEZONE_UTC, eOnThatDay.getDtEndTzID()); - - // event with start+end date for all-day event (one day) - assertEquals(868838400000L, eAllDay1Day.getDtStartInMillis()); - assertEquals(Time.TIMEZONE_UTC, eAllDay1Day.getDtStartTzID()); - assertEquals(868838400000L + 86400000, eAllDay1Day.getDtEndInMillis()); - assertEquals(Time.TIMEZONE_UTC, eAllDay1Day.getDtEndTzID()); - - // event with start+end date for all-day event (ten days) - assertEquals(868838400000L, eAllDay10Days.getDtStartInMillis()); - assertEquals(Time.TIMEZONE_UTC, eAllDay10Days.getDtStartTzID()); - assertEquals(868838400000L + 10*86400000, eAllDay10Days.getDtEndInMillis()); - assertEquals(Time.TIMEZONE_UTC, eAllDay10Days.getDtEndTzID()); - - // event with start+end date on some day (invalid 0 sec-event) - assertEquals(868838400000L, eAllDay0Sec.getDtStartInMillis()); - assertEquals(Time.TIMEZONE_UTC, eAllDay0Sec.getDtStartTzID()); - // DTEND invalid in VEVENT, must have been set to DTSTART+1 day - assertEquals(868838400000L + 86400000, eAllDay0Sec.getDtEndInMillis()); - assertEquals(Time.TIMEZONE_UTC, eAllDay0Sec.getDtEndTzID()); - } - - public void testTimezoneDefToTzId() { - // test valid definition - final String VTIMEZONE_SAMPLE = // taken from RFC 4791, 5.2.2. CALDAV:calendar-timezone Property - "BEGIN:VCALENDAR\n" + - "PRODID:-//Example Corp.//CalDAV Client//EN\n" + - "VERSION:2.0\n" + - "BEGIN:VTIMEZONE\n" + - "TZID:US-Eastern\n" + - "LAST-MODIFIED:19870101T000000Z\n" + - "BEGIN:STANDARD\n" + - "DTSTART:19671029T020000\n" + - "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\n" + - "TZOFFSETFROM:-0400\n" + - "TZOFFSETTO:-0500\n" + - "TZNAME:Eastern Standard Time (US & Canada)\n" + - "END:STANDARD\n" + - "BEGIN:DAYLIGHT\n" + - "DTSTART:19870405T020000\n" + - "RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4\n" + - "TZOFFSETFROM:-0500\n" + - "TZOFFSETTO:-0400\n" + - "TZNAME:Eastern Daylight Time (US & Canada)\n" + - "END:DAYLIGHT\n" + - "END:VTIMEZONE\n" + - "END:VCALENDAR"; - assertEquals("US-Eastern", Event.TimezoneDefToTzId(VTIMEZONE_SAMPLE)); - - // test null value - try { - Event.TimezoneDefToTzId(null); - fail(); - } catch(IllegalArgumentException e) { - assert(true); - } - - // test invalid time zone - try { - Event.TimezoneDefToTzId("/* invalid content */"); - fail(); - } catch(IllegalArgumentException e) { - assert(true); - } - } - - - protected Event parseCalendar(String fname) throws IOException, InvalidResourceException { - @Cleanup InputStream in = assetMgr.open(fname, AssetManager.ACCESS_STREAMING); - Event e = new Event(fname, null); - e.parseEntity(in); - return e; - } -} diff --git a/test/src/at/bitfire/davdroid/resource/test/LocalCalendarTest.java b/test/src/at/bitfire/davdroid/resource/test/LocalCalendarTest.java deleted file mode 100644 index 6fe5afea..00000000 --- a/test/src/at/bitfire/davdroid/resource/test/LocalCalendarTest.java +++ /dev/null @@ -1,154 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.resource.test; - -import java.util.Calendar; - -import lombok.Cleanup; -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.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.RemoteException; -import android.provider.CalendarContract; -import android.provider.CalendarContract.Attendees; -import android.provider.CalendarContract.Calendars; -import android.provider.CalendarContract.Events; -import android.provider.CalendarContract.Reminders; -import android.test.InstrumentationTestCase; -import android.util.Log; -import at.bitfire.davdroid.resource.LocalCalendar; -import at.bitfire.davdroid.resource.LocalStorageException; - -public class LocalCalendarTest extends InstrumentationTestCase { - - private static final String - TAG = "davroid.LocalCalendarTest", - calendarName = "DAVdroid_Test"; - - ContentProviderClient providerClient; - Account testAccount = new Account(calendarName, CalendarContract.ACCOUNT_TYPE_LOCAL); - LocalCalendar testCalendar; - - - // helpers - - private Uri syncAdapterURI(Uri uri) { - return uri.buildUpon() - .appendQueryParameter(Calendars.ACCOUNT_NAME, calendarName) - .appendQueryParameter(Calendars.ACCOUNT_TYPE, CalendarContract.ACCOUNT_TYPE_LOCAL) - .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true"). - build(); - } - - private long insertNewEvent() throws LocalStorageException, RemoteException { - ContentValues values = new ContentValues(); - values.put(Events.CALENDAR_ID, testCalendar.getId()); - values.put(Events.TITLE, "Test Event"); - values.put(Events.ALL_DAY, 0); - values.put(Events.DTSTART, Calendar.getInstance().getTimeInMillis()); - values.put(Events.DTEND, Calendar.getInstance().getTimeInMillis()); - values.put(Events.EVENT_TIMEZONE, "UTC"); - values.put(Events.DIRTY, 1); - return ContentUris.parseId(providerClient.insert(syncAdapterURI(Events.CONTENT_URI), values)); - } - - private void deleteEvent(long id) throws RemoteException { - providerClient.delete(syncAdapterURI(ContentUris.withAppendedId(Events.CONTENT_URI, id)), null, null); - } - - - // initialization - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) - protected void setUp() throws Exception { - ContentResolver resolver = getInstrumentation().getContext().getContentResolver(); - providerClient = resolver.acquireContentProviderClient(CalendarContract.AUTHORITY); - - long id; - - @Cleanup Cursor cursor = providerClient.query(Calendars.CONTENT_URI, - new String[] { Calendars._ID }, - Calendars.ACCOUNT_TYPE + "=? AND " + Calendars.NAME + "=?", - new String[] { CalendarContract.ACCOUNT_TYPE_LOCAL, calendarName }, - null); - if (cursor.moveToNext()) { - // found local test calendar - id = cursor.getLong(0); - Log.d(TAG, "Found test calendar with ID " + id); - - } else { - // no local test calendar found, create - ContentValues values = new ContentValues(); - values.put(Calendars.ACCOUNT_NAME, testAccount.name); - values.put(Calendars.ACCOUNT_TYPE, testAccount.type); - values.put(Calendars.NAME, calendarName); - values.put(Calendars.CALENDAR_DISPLAY_NAME, calendarName); - values.put(Calendars.CALENDAR_ACCESS_LEVEL, Calendars.CAL_ACCESS_OWNER); - values.put(Calendars.ALLOWED_REMINDERS, Reminders.METHOD_ALERT); - values.put(Calendars.SYNC_EVENTS, 0); - 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); - } - - Uri calendarURI = providerClient.insert(syncAdapterURI(Calendars.CONTENT_URI), values); - - id = ContentUris.parseId(calendarURI); - Log.d(TAG, "Created test calendar with ID " + id); - } - - testCalendar = new LocalCalendar(testAccount, providerClient, id, null); - } - - protected void tearDown() throws Exception { - Uri uri = ContentUris.withAppendedId(syncAdapterURI(Calendars.CONTENT_URI), testCalendar.getId()); - providerClient.delete(uri, null, null); - } - - - // tests - - public void testCTags() throws LocalStorageException { - assertNull(testCalendar.getCTag()); - - final String cTag = "just-modified"; - testCalendar.setCTag(cTag); - - assertEquals(cTag, testCalendar.getCTag()); - } - - public void testFindNew() throws LocalStorageException, RemoteException { - // at the beginning, there are no dirty events - assertTrue(testCalendar.findNew().length == 0); - assertTrue(testCalendar.findUpdated().length == 0); - - // insert a "new" event - long id = insertNewEvent(); - try { - // there must be one "new" event now - assertTrue(testCalendar.findNew().length == 1); - assertTrue(testCalendar.findUpdated().length == 0); - - // nothing has changed, the record must still be "new" - // see issue #233 - assertTrue(testCalendar.findNew().length == 1); - assertTrue(testCalendar.findUpdated().length == 0); - } finally { - deleteEvent(id); - } - } - -} diff --git a/test/src/at/bitfire/davdroid/syncadapter/DavResourceFinderTest.java b/test/src/at/bitfire/davdroid/syncadapter/DavResourceFinderTest.java deleted file mode 100644 index 85f7e1e9..00000000 --- a/test/src/at/bitfire/davdroid/syncadapter/DavResourceFinderTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package at.bitfire.davdroid.syncadapter; - -import java.io.IOException; -import java.net.URI; -import java.net.URL; -import java.util.List; - -import android.test.InstrumentationTestCase; -import at.bitfire.davdroid.resource.DavResourceFinder; -import at.bitfire.davdroid.resource.ServerInfo; -import at.bitfire.davdroid.resource.ServerInfo.ResourceInfo; -import at.bitfire.davdroid.test.Constants; -import ezvcard.VCardVersion; - -public class DavResourceFinderTest extends InstrumentationTestCase { - - DavResourceFinder finder; - - @Override - protected void setUp() { - finder = new DavResourceFinder(getInstrumentation().getContext()); - } - - @Override - protected void tearDown() throws IOException { - finder.close(); - } - - - public void testFindResourcesRobohydra() throws Exception { - ServerInfo info = new ServerInfo(new URI(Constants.ROBOHYDRA_BASE), "test", "test", true); - finder.findResources(info); - - // CardDAV - assertTrue(info.isCardDAV()); - List collections = info.getAddressBooks(); - assertEquals(1, collections.size()); - - assertEquals("Default Address Book", collections.get(0).getDescription()); - assertEquals(VCardVersion.V4_0, collections.get(0).getVCardVersion()); - - // CalDAV - assertTrue(info.isCalDAV()); - collections = info.getCalendars(); - assertEquals(2, collections.size()); - - ResourceInfo resource = collections.get(0); - assertEquals("Private Calendar", resource.getTitle()); - assertEquals("This is my private calendar.", resource.getDescription()); - assertFalse(resource.isReadOnly()); - - resource = collections.get(1); - assertEquals("Work Calendar", resource.getTitle()); - assertTrue(resource.isReadOnly()); - } - - - public void testGetInitialContextURL() throws Exception { - // without SRV records, but with well-known paths - ServerInfo roboHydra = new ServerInfo(new URI(Constants.ROBOHYDRA_BASE), "test", "test", true); - assertEquals(new URL(Constants.roboHydra, "/"), finder.getInitialContextURL(roboHydra, "caldav")); - assertEquals(new URL(Constants.roboHydra, "/"), finder.getInitialContextURL(roboHydra, "carddav")); - - // with SRV records and well-known paths - ServerInfo iCloud = new ServerInfo(new URI("mailto:test@icloud.com"), "", "", true); - assertEquals(new URL("https://contacts.icloud.com/"), finder.getInitialContextURL(iCloud, "carddav")); - assertEquals(new URL("https://caldav.icloud.com/"), finder.getInitialContextURL(iCloud, "caldav")); - } - -} diff --git a/test/src/at/bitfire/davdroid/test/ArrayUtilsTest.java b/test/src/at/bitfire/davdroid/test/ArrayUtilsTest.java deleted file mode 100644 index 2761d2be..00000000 --- a/test/src/at/bitfire/davdroid/test/ArrayUtilsTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.test; -import java.util.Arrays; - -import junit.framework.TestCase; -import at.bitfire.davdroid.ArrayUtils; - - -public class ArrayUtilsTest extends TestCase { - - public void testPartition() { - // n == 0 - assertTrue(Arrays.deepEquals( - new Long[0][0], - ArrayUtils.partition(new Long[] { }, 5))); - - // n < max - assertTrue(Arrays.deepEquals( - new Long[][] { { 1l, 2l } }, - ArrayUtils.partition(new Long[] { 1l, 2l }, 5))); - - // n == max - assertTrue(Arrays.deepEquals( - new Long[][] { { 1l, 2l }, { 3l, 4l } }, - ArrayUtils.partition(new Long[] { 1l, 2l, 3l, 4l }, 2))); - - // n > max - assertTrue(Arrays.deepEquals( - new Long[][] { { 1l, 2l, 3l, 4l, 5l }, { 6l, 7l, 8l, 9l, 10l }, { 11l } }, - ArrayUtils.partition(new Long[] { 1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l, 9l, 10l, 11l }, 5))); - } - -} diff --git a/test/src/at/bitfire/davdroid/test/Constants.java b/test/src/at/bitfire/davdroid/test/Constants.java deleted file mode 100644 index 707762b3..00000000 --- a/test/src/at/bitfire/davdroid/test/Constants.java +++ /dev/null @@ -1,19 +0,0 @@ -package at.bitfire.davdroid.test; - -import java.net.MalformedURLException; -import java.net.URL; - -import android.util.Log; - -public class Constants { - public static final String ROBOHYDRA_BASE = "http://10.0.0.11:3000/"; - - public static URL roboHydra; - static { - try { - roboHydra = new URL(ROBOHYDRA_BASE); - } catch(MalformedURLException e) { - Log.wtf("davdroid.test.Constants", "Invalid RoboHydra base URL"); - } - } -} diff --git a/test/src/at/bitfire/davdroid/test/ContactTest.java b/test/src/at/bitfire/davdroid/test/ContactTest.java deleted file mode 100644 index a98ed78f..00000000 --- a/test/src/at/bitfire/davdroid/test/ContactTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.test; - -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); - - return c; - } -} diff --git a/test/src/at/bitfire/davdroid/test/URLUtilsTest.java b/test/src/at/bitfire/davdroid/test/URLUtilsTest.java deleted file mode 100644 index 555b9b6b..00000000 --- a/test/src/at/bitfire/davdroid/test/URLUtilsTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.test; - -import java.net.URL; - -import junit.framework.TestCase; -import at.bitfire.davdroid.URLUtils; - -public class URLUtilsTest extends TestCase { - - public void testEnsureTrailingSlash() throws Exception { - assertEquals("/test/", URLUtils.ensureTrailingSlash("/test")); - assertEquals("/test/", URLUtils.ensureTrailingSlash("/test/")); - - String withoutSlash = "http://www.test.at/dav/collection", - withSlash = withoutSlash + "/"; - assertEquals(new URL(withSlash), URLUtils.ensureTrailingSlash(new URL(withoutSlash))); - assertEquals(new URL(withSlash), URLUtils.ensureTrailingSlash(new URL(withSlash))); - } - - public void testSanitize() { - assertNull(URLUtils.sanitize(null)); - - // escape "@" - assertEquals("https://my%40server/my%40email.com/dir", URLUtils.sanitize("https://my@server/my@email.com/dir")); - assertEquals("http://my%40server/my%40email.com/dir", URLUtils.sanitize("http://my@server/my@email.com/dir")); - assertEquals("//my%40server/my%40email.com/dir", URLUtils.sanitize("//my@server/my@email.com/dir")); - assertEquals("/my%40email.com/dir", URLUtils.sanitize("/my@email.com/dir")); - assertEquals("my%40email.com/dir", URLUtils.sanitize("my@email.com/dir")); - - // escape ":" in path but not as port separator - assertEquals("https://www.test.at:80/my%3afile.vcf", URLUtils.sanitize("https://www.test.at:80/my:file.vcf")); - assertEquals("http://www.test.at:80/my%3afile.vcf", URLUtils.sanitize("http://www.test.at:80/my:file.vcf")); - assertEquals("//www.test.at:80/my%3afile.vcf", URLUtils.sanitize("//www.test.at:80/my:file.vcf")); - assertEquals("/my%3afile.vcf", URLUtils.sanitize("/my:file.vcf")); - assertEquals("my%3afile.vcf", URLUtils.sanitize("my:file.vcf")); - - // keep literal IPv6 addresses (only in host name) - assertEquals("https://[1:2::1]/", URLUtils.sanitize("https://[1:2::1]/")); - assertEquals("/%5b1%3a2%3a%3a1%5d/", URLUtils.sanitize("/[1:2::1]/")); - } -} diff --git a/test/src/at/bitfire/davdroid/webdav/DavRedirectStrategyTest.java b/test/src/at/bitfire/davdroid/webdav/DavRedirectStrategyTest.java deleted file mode 100644 index 7467175f..00000000 --- a/test/src/at/bitfire/davdroid/webdav/DavRedirectStrategyTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package at.bitfire.davdroid.webdav; - -import java.io.IOException; -import java.net.URL; - -import junit.framework.TestCase; -import at.bitfire.davdroid.test.Constants; -import ch.boye.httpclientandroidlib.HttpResponse; -import ch.boye.httpclientandroidlib.client.methods.HttpOptions; -import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest; -import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext; -import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient; -import ch.boye.httpclientandroidlib.impl.client.HttpClientBuilder; -import ch.boye.httpclientandroidlib.protocol.HttpContext; - -public class DavRedirectStrategyTest extends TestCase { - - CloseableHttpClient httpClient; - DavRedirectStrategy strategy = DavRedirectStrategy.INSTANCE; - - @Override - protected void setUp() { - httpClient = HttpClientBuilder.create() - .useSystemProperties() - .disableRedirectHandling() - .build(); - } - - @Override - protected void tearDown() throws IOException { - httpClient.close(); - } - - - // happy cases - - public void testNonRedirection() throws Exception { - HttpUriRequest request = new HttpOptions(Constants.roboHydra.toURI()); - HttpResponse response = httpClient.execute(request); - assertFalse(strategy.isRedirected(request, response, null)); - } - - public void testDefaultRedirection() throws Exception { - final String newLocation = "/new-location"; - - HttpContext context = HttpClientContext.create(); - HttpUriRequest request = new HttpOptions(new URL(Constants.roboHydra, "redirect/301?to=" + newLocation).toURI()); - HttpResponse response = httpClient.execute(request, context); - assertTrue(strategy.isRedirected(request, response, context)); - - HttpUriRequest redirected = strategy.getRedirect(request, response, context); - assertEquals(new URL(Constants.roboHydra, newLocation).toURI(), redirected.getURI()); - } - - - // error cases - - public void testMissingLocation() throws Exception { - HttpContext context = HttpClientContext.create(); - HttpUriRequest request = new HttpOptions(new URL(Constants.roboHydra, "redirect/without-location").toURI()); - HttpResponse response = httpClient.execute(request, context); - assertFalse(strategy.isRedirected(request, response, context)); - } - - public void testRelativeLocation() throws Exception { - HttpContext context = HttpClientContext.create(); - HttpUriRequest request = new HttpOptions(new URL(Constants.roboHydra, "redirect/relative").toURI()); - HttpResponse response = httpClient.execute(request, context); - assertTrue(strategy.isRedirected(request, response, context)); - - HttpUriRequest redirected = strategy.getRedirect(request, response, context); - assertEquals(new URL(Constants.roboHydra, "/new/location").toURI(), redirected.getURI()); - } -} diff --git a/test/src/at/bitfire/davdroid/webdav/TlsSniSocketFactoryTest.java b/test/src/at/bitfire/davdroid/webdav/TlsSniSocketFactoryTest.java deleted file mode 100644 index f00844d5..00000000 --- a/test/src/at/bitfire/davdroid/webdav/TlsSniSocketFactoryTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package at.bitfire.davdroid.webdav; - -import java.io.IOException; - -import javax.net.ssl.SSLSocket; - -import android.util.Log; -import junit.framework.TestCase; - -public class TlsSniSocketFactoryTest extends TestCase { - private static final String TAG = "davdroid.TlsSniSocketFactoryTest"; - - public void testCiphers() throws IOException { - SSLSocket socket = (SSLSocket)TlsSniSocketFactory.INSTANCE.createSocket(null); - - Log.i(TAG, "Enabled:"); - for (String cipher : socket.getEnabledCipherSuites()) - Log.i(TAG, cipher); - - Log.i(TAG, "Supported:"); - for (String cipher : socket.getSupportedCipherSuites()) - Log.i(TAG, cipher); - - assert(true); - } - -} diff --git a/test/src/at/bitfire/davdroid/webdav/WebDavResourceTest.java b/test/src/at/bitfire/davdroid/webdav/WebDavResourceTest.java deleted file mode 100644 index 4a5b0510..00000000 --- a/test/src/at/bitfire/davdroid/webdav/WebDavResourceTest.java +++ /dev/null @@ -1,234 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 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.webdav; - -import java.io.InputStream; -import java.net.URL; -import java.util.Arrays; -import java.util.List; - -import javax.net.ssl.SSLPeerUnverifiedException; - -import lombok.Cleanup; - -import org.apache.commons.io.IOUtils; - -import android.content.res.AssetManager; -import android.test.InstrumentationTestCase; -import at.bitfire.davdroid.test.Constants; -import at.bitfire.davdroid.webdav.HttpPropfind.Mode; -import at.bitfire.davdroid.webdav.WebDavResource.PutMode; -import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient; - -// tests require running robohydra! - -public class WebDavResourceTest extends InstrumentationTestCase { - static byte[] SAMPLE_CONTENT = new byte[] { 1, 2, 3, 4, 5 }; - - final static String PATH_SIMPLE_FILE = "collection/new.file"; - - AssetManager assetMgr; - CloseableHttpClient httpClient; - - WebDavResource baseDAV; - WebDavResource simpleFile, - davCollection, davNonExistingFile, davExistingFile, - davInvalid; - - @Override - protected void setUp() throws Exception { - httpClient = DavHttpClient.create(true, true); - - assetMgr = getInstrumentation().getContext().getResources().getAssets(); - - baseDAV = new WebDavResource(httpClient, new URL(Constants.roboHydra, "/dav/")); - - simpleFile = new WebDavResource(httpClient, new URL(Constants.ROBOHYDRA_BASE + "assets/test.random")); - - davCollection = new WebDavResource(httpClient, new URL(Constants.ROBOHYDRA_BASE + "dav/")); - davNonExistingFile = new WebDavResource(davCollection, "collection/new.file"); - davExistingFile = new WebDavResource(davCollection, "collection/existing.file"); - - davInvalid = new WebDavResource(httpClient, new URL(Constants.ROBOHYDRA_BASE + "dav-invalid/")); - } - - @Override - protected void tearDown() throws Exception { - httpClient.close(); - } - - - /* test feature detection */ - - public void testOptions() throws Exception { - String[] davMethods = new String[] { "PROPFIND", "GET", "PUT", "DELETE", "REPORT" }, - davCapabilities = new String[] { "addressbook", "calendar-access" }; - - WebDavResource capable = new WebDavResource(baseDAV); - capable.options(); - for (String davMethod : davMethods) - assert(capable.supportsMethod(davMethod)); - for (String capability : davCapabilities) - assert(capable.supportsDAV(capability)); - } - - public void testPropfindCurrentUserPrincipal() throws Exception { - davCollection.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL); - assertEquals("/dav/principals/users/test", davCollection.getCurrentUserPrincipal()); - - try { - simpleFile.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL); - fail(); - - } catch(DavException ex) { - } - assertNull(simpleFile.getCurrentUserPrincipal()); - } - - public void testPropfindHomeSets() throws Exception { - WebDavResource dav = new WebDavResource(davCollection, "principals/users/test"); - dav.propfind(HttpPropfind.Mode.HOME_SETS); - assertEquals("/dav/addressbooks/test/", dav.getAddressbookHomeSet()); - assertEquals("/dav/calendars/test/", dav.getCalendarHomeSet()); - } - - public void testPropfindAddressBooks() throws Exception { - WebDavResource dav = new WebDavResource(davCollection, "addressbooks/test"); - dav.propfind(HttpPropfind.Mode.CARDDAV_COLLECTIONS); - assertEquals(2, dav.getMembers().size()); - for (WebDavResource member : dav.getMembers()) { - if (member.getName().equals("default-v4.vcf")) - assertTrue(member.isAddressBook()); - else - assertFalse(member.isAddressBook()); - assertFalse(member.isCalendar()); - } - } - - public void testPropfindCalendars() throws Exception { - WebDavResource dav = new WebDavResource(davCollection, "calendars/test"); - dav.propfind(Mode.CALDAV_COLLECTIONS); - assertEquals(3, dav.getMembers().size()); - assertEquals("0xFF00FF", dav.getMembers().get(2).getColor()); - for (WebDavResource member : dav.getMembers()) { - if (member.getName().contains(".ics")) - assertTrue(member.isCalendar()); - else - assertFalse(member.isCalendar()); - assertFalse(member.isAddressBook()); - } - } - - public void testPropfindTrailingSlashes() throws Exception { - final String principalOK = "/principals/ok"; - - String requestPaths[] = { - "/dav/collection-response-with-trailing-slash", - "/dav/collection-response-with-trailing-slash/", - "/dav/collection-response-without-trailing-slash", - "/dav/collection-response-without-trailing-slash/" - }; - - for (String path : requestPaths) { - WebDavResource davSlash = new WebDavResource(davCollection, path); - davSlash.propfind(Mode.CARDDAV_COLLECTIONS); - assertEquals(principalOK, davSlash.getCurrentUserPrincipal()); - } - } - - - /* test normal HTTP/WebDAV */ - - public void testPropfindRedirection() throws Exception { - // PROPFIND redirection - WebDavResource redirected = new WebDavResource(baseDAV, "/redirect/301?to=/dav/"); - redirected.propfind(Mode.CURRENT_USER_PRINCIPAL); - assertEquals("/dav/", redirected.getLocation().getPath()); - } - - public void testGet() throws Exception { - simpleFile.get("*/*"); - @Cleanup InputStream is = assetMgr.open("test.random", AssetManager.ACCESS_STREAMING); - byte[] expected = IOUtils.toByteArray(is); - assertTrue(Arrays.equals(expected, simpleFile.getContent())); - } - - public void testGetHttpsWithSni() throws Exception { - WebDavResource file = new WebDavResource(httpClient, new URL("https://sni.velox.ch")); - - boolean sniWorking = false; - try { - file.get("*/*"); - sniWorking = true; - } catch (SSLPeerUnverifiedException e) { - } - - assertTrue(sniWorking); - } - - public void testMultiGet() throws Exception { - WebDavResource davAddressBook = new WebDavResource(davCollection, "addressbooks/default.vcf"); - davAddressBook.multiGet(DavMultiget.Type.ADDRESS_BOOK, new String[] { "1.vcf", "2.vcf" }); - assertEquals(2, davAddressBook.getMembers().size()); - for (WebDavResource member : davAddressBook.getMembers()) { - assertNotNull(member.getContent()); - } - } - - public void testPutAddDontOverwrite() throws Exception { - // should succeed on a non-existing file - assertEquals("has-just-been-created", davNonExistingFile.put(SAMPLE_CONTENT, PutMode.ADD_DONT_OVERWRITE)); - - // should fail on an existing file - try { - davExistingFile.put(SAMPLE_CONTENT, PutMode.ADD_DONT_OVERWRITE); - fail(); - } catch(PreconditionFailedException ex) { - } - } - - public void testPutUpdateDontOverwrite() throws Exception { - // should succeed on an existing file - assertEquals("has-just-been-updated", davExistingFile.put(SAMPLE_CONTENT, PutMode.UPDATE_DONT_OVERWRITE)); - - // should fail on a non-existing file - try { - davNonExistingFile.put(SAMPLE_CONTENT, PutMode.UPDATE_DONT_OVERWRITE); - fail(); - } catch(PreconditionFailedException ex) { - } - } - - public void testDelete() throws Exception { - // should succeed on an existing file - davExistingFile.delete(); - - // should fail on a non-existing file - try { - davNonExistingFile.delete(); - fail(); - } catch (NotFoundException e) { - } - } - - - /* test CalDAV/CardDAV */ - - - /* special test */ - - public void testInvalidURLs() throws Exception { - WebDavResource dav = new WebDavResource(davInvalid, "addressbooks/user%40domain/"); - dav.propfind(HttpPropfind.Mode.CARDDAV_COLLECTIONS); - List members = dav.getMembers(); - assertEquals(2, members.size()); - assertEquals(Constants.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()); - } - -}