These steps are performed when synchronizing a collection:
- Prepare synchronization: prepare local collection, settings etc.
- Query capabilities with HTTP
PROPFIND:- determine whether the server supports Collection Synchronization
- CardDAV: determine whether the server supports vCard 4
- fetch current
CTagandsync-token
- Process locally deleted resources: if a local resource is flagged as deleted,
- delete it on the server (HTTP
DELETEwithIf-Match/If-Schedule-Tag-Matchset to last knownETag/Schedule-Tagto avoid deleting resources which have been changed on the server in the meanwhile) and - then remove it locally
- delete it on the server (HTTP
- Upload locally modified ("dirty") resources:
- Assign a random UID (if necessary) and resource name to new resources; prepare contact group and recurring events, if necessary
- If no previous
ETag/Schedule-Tagof the resource is known (i.e. the resource has not been uploaded yet), use HTTPPUTwithIf-None-Match: *to avoid overwriting a possibly existing resource with the same name - If a previous
ETag/Schedule-Tagof the resource is known, use HTTPPUTwithIf-Match/If-Schedule-Tag-Matchset to last knownETag/Schedule-Tagto avoid overwriting changes which happend on the server in the meanwhile - remember returned
ETag/Schedule-Tagas last knownETag/Schedule-Tag; otherwise reset last knownETag/Schedule-Tag
- Choose sync algorithm (
PROPFIND/REPORTvs. Collection Synchronization):- CardDAV: use Collection Synchronization if supported by server,
PROPFINDotherwise - CalDAV events: use Collection Synchronization if supported by server and past time event limit is disabled,
REPORT calendar-queryotherwise - CalDAV tasks: use
REPORT calendar-query
- CardDAV: use Collection Synchronization if supported by server,
- Check whether further synchronization is needed. Only continue when:
- modifications (uploads/deletions) have been sent to the server, or
- the sync state (
CTag/sync-token) of the collection has changed since last sync, or - the
PROPFIND/REPORTalgorithm shall be used and the sync has been initiated manually
- Continue with chosen sync algorithm (see below).
- Unset present remotely flag for all resources.
- List and process remote resources (only names and
ETag) usingPROPFINDorREPORT(see above).- Download resources which have been added/modified remotely in bunches using
REPORT addressbook-multiget/calendar-multigetinto the local storage. - Set present remotely flag for all received resources.
- Download resources which have been added/modified remotely in bunches using
- Locally delete all resources which are not flagged as present remotely.
- Post-processing: clean up empty contact groups etc.
- Save sync state (
CTag/sync-token).
- Was a previous initial sync aborted and is now being continued? → If yes, set initial sync.
- Do we have a previous
sync-token? → If no, set initial sync. - List and process changes since last
sync-token(or all records if no previoussync-tokenis known) usingREPORT sync-collection.- Download resources which have been added/modified remotely in bunches using
REPORT addressbook-multiget/calendar-multigetinto the local storage. - Set present remotely flag for all received resources.
- Download resources which have been added/modified remotely in bunches using
- If the requested
sync-tokenwas invalid:- forget the
sync-token - reset present remotely flags of all local resources
- set initial sync and continue with 3.
- forget the
- Save
sync-token. - Are there further changes on the server (HTTP 507 on collection URL in multiget response)? → If yes, continue with 3.
- Only for initial sync: delete all local resources which are not present remotely.
- Post-processing: clean up empty contact groups etc.
Conflicts occur when different versions of a resource are available and it's ambigous which one shall be used. For instance:
- A contact exists on the server and has been synchronized to a mobile phone with DAVx⁵ and to a desktop PC with Gnome Evolution.
- You modify the contact with Evolution, which immediately uploads it to the server.
- You modify the same contact on your mobile device, too.
- DAVx⁵ wants to upload the modified contact and finds that it has been changed on the server in the meanwhile. Now there's a conflict of two different versions of this contact.
How DAVx⁵ handles such conflicts:
- DAVx⁵ relies on HTTP
ETagto determine whether a resource has been changed on the server. - The server always wins. If a local resource can't be uploaded or deleted safely because it has been modified on the server in the meanwhile, local changes are discarded and the server version is used.
- DAVx⁵ doesn't involve the user in resolving conflicts (like asking which version shall be used) because it's supposed to run in the background silently.
At the moment, DAVx⁵ doesn't support automatic vCard merging as suggested in vCard 4.
These vCard properties are mapped to ContactsContract.CommonDataKinds.StructuredName records:
FN↔ display nameN↔ prefix, given name, middle name, family name, suffixX-PHONETIC-FIRST-NAME↔ phonetic given nameX-PHONETIC-MIDDLE-NAME↔ phonetic middle nameX-PHONETIC-LAST-NAME↔ phonetic first name
These vCard properties are mapped to ContactsContract.CommonDataKinds.Nickname records:
NICKNAME↔ nick name (types are mapped asTYPEx-values)
vCard TEL properties are mapped to ContactsContract.CommonDataKinds.Phone records (phone number).
vCard EMAIL properties are mapped to ContactsContract.CommonDataKinds.Email records (email address).
vCard PHOTO properties are mapped to ContactsContract.CommonDataKinds.Photo records.
Because of Android limitations, contact photos with more than 1 MB can't be stored in the Android contacts provider, so DAVx⁵ has to resize large vCard photos to the values given by CONTENT_MAX_DIMENSIONS_URI. This limit does not apply in the other direction (Android → vCard).
These vCard properties are mapped to ContactsContract.CommonDataKinds.Organization records:
ORG↔ company, departmentTITLE↔ (job) titleROLE↔ job description
vCard IMPP properties are mapped to ContactsContract.CommonDataKinds.Im
(messenger account) and – if the URI scheme is sip: – ContactsContract.CommonDataKinds.SipAddress (SIP address) records.
When importing a vCard, X-SIP values are treated like IMPP:sip:... and stored as SIP address.
vCard NOTE properties are mapped to ContactsContract.CommonDataKinds.Note records (note).
These vCard properties are mapped to ContactsContract.CommonDataKinds.StructuredPostal records:
ADR↔ street address, p/o box, extended address, locality, region, postal code, country, vCard 4: formatted addressLABEL↔ vCard3: formatted address
If a vCard doesn't contain a formatted address, it will be generated by DAVx⁵ in this format:
street po.box (extended)
postcode city
region
COUNTRY
vCard URL properties are mapped to ContactsContract.CommonDataKinds.Website records (Web site).
These vCard properties are mapped to ContactsContract.CommonDataKinds.Event records:
BDAY↔ birthdayANNIVERSARY↔ anniversary
Partial dates without year are supported.
vCard RELATED properties are mapped to ContactsContract.CommonDataKinds.Relation records (relation).
Not all vCard values have a corresponding Android value and vice versa. Custom relation names are supported.
If the Groups are per-contact categories method is set in the account settings, DAVx⁵ will match contact groups
to CATEGORIES. For instance, when a contact is in the groups "Friends" and "Family", this property will be added: CATEGORIES:Friends,Family.
If the Groups are separate vCards method is set in the account settings, DAVx⁵ will use
KIND(orX-ADDRESSBOOKSERVER-KINDif the server doesn't support VCard 4) to distinguish between contacts and contact groups, andMEMBER(orX-ADDRESSBOOKSERVER-MEMBERif the server doesn't support VCard 4) to store contact group members.
For some properties, custom labels are supported by vCard property groups. For custom labels, the X-ABLABEL property is used like that:
BEGIN:VCARD
...
davdroid1.TEL:+123456
davdroid1.X-ABLABEL:My Custom Phone
davdroid2.EMAIL:test@example.com
davdroid2.X-ABLABEL:My Custom Email Address
...
END:VCARD
In this example, the phone number +123456 is grouped together with the custom label "My Custom Phone" and the email address test@example.com is labelled "My Custom Email Address".
Contact properties which are not processed by DAVx⁵ (like X- properties) are retained. When importing a vCard, DAVx⁵ saves all unknown properties.
When the respective contact is modified and DAVx⁵ generates the vCard again, it starts with all unknown properties and then adds the known ones.
These vCard properties are processed/generated by DAVx⁵ and cannot be changed by users:
PRODIDis set to the DAVx⁵ identifierUIDis used to identify a vCard (for new vCards, a random UUID will be generated)REVis set to the current time when generating a vCardSOURCEis removed because it doesn't apply anymore as soon as DAVx⁵ generates the vCardLOGO,SOUNDare removed because retaining them might cause out-of-memory errors
Events are stored as CalendarContract.Events in the Android calendar provider. These iCalendar properties are directly mapped to Android fields:
SUMMARY↔ titleLOCATION↔ event locationDESCRIPTION↔ descriptionCOLOR↔ event color (only if enabled in DAVx⁵ account settings)DTSTART↔ start date/time, event timezone / all-day eventDTEND,DURATION↔ end date/time, event end timezone / all-day eventCLASS↔ :ref:`access level <access-level>`TRANSP↔ availability (opaque ↔ busy, transparent ↔ free)STATUS↔ status (confirmed/tentative/cancelled)
Events are considered to be all-day events when DTSTART is a date (and not a time). All-day events
- without end date or
- with an end date that is not after the start date
are stored with a duration of one day for Android compatibility.
VALARM components are mapped to CalendarContract.Reminders records and vice versa.
Reminder methods (ACTION) are mapped to Android values as good as possible.
RRULE, RDATE, EXRULE and EXDATE values are stored in the respective Android event fields. The Android calendar provider uses these fields to calculcate the instances of a recurring event, which are then saved as CalendarContract.Instances so that calendar apps can access them.
Exceptions of recurring events are identified by RECURRENCE-ID. DAVx⁵ inserts exceptions as separate event records with ORIGINAL_SYNC_ID set to the SYNC_ID of the recurring event and ORIGINAL_TIME set to the RECURRENCE-ID value.
Note
DAVx⁵ is not responsible for calculating the instances of a recurring event.
It only provides RRULE, RDATE, EXRULE, EXDATE and a list of exceptions to the Android calendar provider.
However, if a calendar app leaves dirty events with zero instances to be synced, DAVx⁵ will silently delete these events and ask the server to do the same.
ATTENDEE properties are mapped to CalendarContract.AttendeesColumns records and vice versa.
Events with at least one attendee are considered to be group-scheduled events. Only for group-scheduled events, the ORGANIZER property
- is imported from iCalendars to the Android event so that only the organizer can edit a group-scheduled event,
- is exported from the Android event to the iCalendar.
When you add attendees to an event, the server may send invitations to the attendees (for instance, by email). DAVx⁵ doesn't send invitation emails on its own.
When processing a downloaded event, DAVx⁵ normalizes event date/time values to Android-compatible formats before storing them in the Android calendar
provider. Outlook/Windows TZIDs (e.g., W. Europe Standard Time) are mapped to IANA/Android TZIDs (e.g., Europe/Berlin).
Stored events use Android/system time zones:
DATE-TIMEwithTZID=...: if the TZID is known to Android, DAVx⁵ keeps the same local date and time, but stores it with Android's/system's timezone definition for that TZID.- UTC
DATE-TIME(...Z): stored as UTC. - floating
DATE-TIME(no TZID): stored in the current system default time zone. - all-day events (
VALUE=DATE): stored as UTC dates, as required by the Android calendar provider.
If a TZID is not available in Android, DAVx⁵ tries to match it to a system time zone by name heuristics. If that is not possible,
DAVx⁵ keeps the same point in time and stores the event in the system default time zone instead. Custom VTIMEZONE
definitions are therefore used to interpret incoming date and time values, but custom TZIDs are not preserved unless they can be
mapped to a system timezone. If an incoming VTIMEZONE contains outdated or conflicting rules for a TZID that Android
already knows, the stored event still uses Android's system time zone definition for that TZID, not the definition from
the iCalendar.
When generating an event to upload, DAVx⁵ recreates event date/time values from the Android event record:
- all-day events: exported as
DATEvalues - UTC events: exported as UTC
DATE-TIMEvalues - other timed events: exported as
DATE-TIMEvalues withTZID=...
For every referenced non-UTC TZID, DAVx⁵ also generates an outgoing VTIMEZONE from the bundled ical4j time zone registry and
minifies it, retaining only the relevant daylight saving observances.
Stored events therefore use Android/system time zone definitions, while generated VTIMEZONE components come from
the ical4j library and its bundled time zone definitions shipped with DAVx⁵. If those databases differ, known TZIDs are
still usually interpreted by clients via their own TZ database, but edge cases can remain, especially for recurring events
with unknown/custom time zones.
Warning
Because the Android calendar provider can only process events with time zones which are available in Android, recurring events in time zones which are not available in Android and their exceptions may not be expanded correctly.
iCalendar event classification is mapped to Android's ACCESS_LEVEL like that:
- no
CLASS→ACCESS_LEVEL=ACCESS_DEFAULT("server default") CLASS:PUBLIC→ACCESS_LEVEL=ACCESS_PUBLIC("public")CLASS:PRIVATE→ACCESS_LEVEL=ACCESS_PRIVATE("private")CLASS:CONFIDENTIAL→ACCESS_LEVEL=ACCESS_CONFIDENTIAL(currently not supported by many calendar apps, which will reset the access level toACCESS_DEFAULTorACCESS_PRIVATEwhen the event is edited); additionally,CONFIDENTIALis stored as original value- other
CLASSvalue (x-name or iana-token) →ACCESS_LEVEL=ACCESS_PRIVATE; additionally, the value is stored as original value
In the other direction, the locally stored access level is mapped to CLASS like that:
ACCESS_LEVEL=ACCESS_PUBLIC("public") →CLASS:PUBLICACCESS_LEVEL=ACCESS_PRIVATE("private") →CLASS:PRIVATEACCESS_LEVEL=ACCESS_CONFIDENTIAL("confidential", if available in calendar app) →CLASS:CONFIDENTIALACCESS_LEVEL=ACCESS_DEFAULT("server default") →- if there is an original value: use that value
- no
CLASSotherwise (same asPUBLIC)
.. versionadded:: 2.6.2 In earlier versions, event categories were treated as unknown properties (see below).
iCalendar CATEGORIES are mapped from/to extended properties
with these fields:
name=categoriesvalue= list of category names, separated by backslash (\), for example:Cat A\Cat B\Cat C. If a category name contains a backslash, the backslash will be dropped silenty.
This is the same format as it is used by the AOSP ActiveSync Exchange sync adapter.
These properties are not natively supported by Android, but are synchronized as extended properties with a format defined by DAVx⁵:
URL
See :ref:`extended event properties <extended_event_properties>` for more information.
iCalendar properties which are not processed by DAVx⁵ (like X- properties) are retained (unless they're larger than ≈ 25 kB).
When importing an iCalendar, DAVx⁵ saves all unknown event properties as extended property rows.
When the respective event is modified and DAVx⁵ generates the iCalendar again, it will include all unknown properties.
These iCalendar properties are processed/generated by DAVx⁵ and cannot be changed by users:
PRODIDis set to the DAVx⁵ identifierUIDis used to identify an iCalendar (for new iCalendars, a random UUID will be generated)RECURRENCE-IDis used to identify certain instances of recurring eventsSEQUENCEis increased when an iCalendar is modifiedDTSTAMPis set to the current time when generating an iCalendar
DAVx⁵ synchronizes VTODO components (= tasks) with the OpenTasks provider, so
OpenTasks or tasks.org
must be installed for task synchronization.
To use some features (for instance, to see subtasks as indented task) in the UI, you may need another tasks app that is able to access the OpenTasks provider, like aCalendar+.
These properties are synchronized by DAVx⁵:
UIDSUMMARY,DESCRIPTIONLOCATIONGEOURLORGANIZERPRIORITYCOMPLETED,PERCENT-COMPLETESTATUSCREATED,LAST-MODIFIEDDTSTART,DUE,DURATIONRDATE,EXDATE,RRULECATEGORIESRELATED-TO(used for subtasks)
See :ref:`unknown properties of events <event-unknown-properties>`.
Detection: DAVx⁵ uses OPTIONS to detect whether a URL is WebDAV-capable. It expects at least compliance class 1 (DAV: 1).
Supported features:
Directory listing (
PROPFIND)- WebDAV quota (RFC 4331) of root directory is shown in DAVx⁵ WebDAV mounts
Random-access read (
GETwithRangeheaders) for files opened with moder- that have a known file size,
- that have an
ETagorLast-Modified, - on Android 8+ only.
This allows things like seeking in videos or extracting data from a file without reading the whole file.
A page cache with a page size of 2 MB is used, so files will be requested in pieces of 2 MB, regardless of the actual read operations.
Atomic (streaming) read (
GET) for files opened with moderthat can't be opened in random-access modeAtomic (streaming) write (
PUT) for files opened with modew
.. versionadded:: 2.5 DAVx⁵ uses `Conscrypt <https://github.com/google/conscrypt/blob/master/CAPABILITIES.md>`_ to support modern TLS protocol versions and ciphers even on older devices. Both your client (DAVx⁵) and the CalDAV/CardDAV server must share at least one cipher, otherwise a ``SSLProtocolException`` will occur.