From af011a65dbc65e6122946be6d6101dd5a9d92b8d Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Mon, 25 May 2015 19:54:16 +0200 Subject: [PATCH] Sync tasks * remove VJOURNAL/notes sync (will be implemented later) * setup: add "install Tasks app" fragment * version bump to 0.8.0-beta1 * use Tasks instead of Mirakel * handle task list colors * allow independent selection of calendar/task sync for the same CalDAV calendar * minor refactoring (don't use return value of Builder) * handle more task fields and time zones * sync interval setting for tasks --- .../res/setup_task_lists_heading.xml | 29 + app/src/main/AndroidManifest.xml | 19 +- .../java/at/bitfire/davdroid/Constants.java | 2 +- .../java/at/bitfire/davdroid/DAVUtils.java | 20 + .../java/at/bitfire/davdroid/DateUtils.java | 14 +- .../davdroid/resource/CalDavNotebook.java | 72 - .../davdroid/resource/DavResourceFinder.java | 30 +- .../davdroid/resource/LocalAddressBook.java | 64 +- .../davdroid/resource/LocalCalendar.java | 61 +- .../davdroid/resource/LocalNotebook.java | 179 --- .../davdroid/resource/LocalTaskList.java | 165 ++- .../bitfire/davdroid/resource/ServerInfo.java | 35 +- .../at/bitfire/davdroid/resource/Task.java | 33 +- .../davdroid/syncadapter/AccountSettings.java | 39 +- .../syncadapter/NotesSyncAdapterService.java | 73 - .../syncadapter/TasksSyncAdapterService.java | 2 - .../davdroid/ui/settings/AccountFragment.java | 34 +- .../ui/setup/AccountDetailsFragment.java | 112 +- .../davdroid/ui/setup/AddAccountActivity.java | 10 + .../ui/setup/InstallAppsFragment.java | 96 ++ .../ui/setup/QueryServerDialogFragment.java | 19 +- .../ui/setup/SelectCollectionsAdapter.java | 106 +- .../ui/setup/SelectCollectionsFragment.java | 23 +- .../notebooks/provider/NoteContract.java | 1 - .../org/dmfs/provider/tasks/TaskContract.java | 1283 +---------------- .../org/dmfs/provider/tasks/UriFactory.java | 1 + .../res/drawable-hdpi/navigation_skip.png | Bin 0 -> 661 bytes .../res/drawable-mdpi/navigation_skip.png | Bin 0 -> 488 bytes .../res/drawable-xhdpi/navigation_skip.png | Bin 0 -> 813 bytes .../res/drawable-xxhdpi/navigation_skip.png | Bin 0 -> 1140 bytes .../setup_task_lists_heading.xml | 29 + .../main/res/layout/setup_install_apps.xml | 45 + .../sync_notes.xml => menu/only_skip.xml} | 18 +- app/src/main/res/values-de/strings.xml | 9 + app/src/main/res/values/strings.xml | 11 +- .../main/res/xml/settings_account_prefs.xml | 7 + app/src/main/res/xml/sync_tasks.xml | 2 +- 37 files changed, 714 insertions(+), 1929 deletions(-) create mode 100644 app/src/androidTest/res/setup_task_lists_heading.xml create mode 100644 app/src/main/java/at/bitfire/davdroid/DAVUtils.java delete mode 100644 app/src/main/java/at/bitfire/davdroid/resource/CalDavNotebook.java delete mode 100644 app/src/main/java/at/bitfire/davdroid/resource/LocalNotebook.java delete mode 100644 app/src/main/java/at/bitfire/davdroid/syncadapter/NotesSyncAdapterService.java create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/setup/InstallAppsFragment.java delete mode 120000 app/src/main/java/at/bitfire/notebooks/provider/NoteContract.java mode change 100644 => 120000 app/src/main/java/org/dmfs/provider/tasks/TaskContract.java create mode 120000 app/src/main/java/org/dmfs/provider/tasks/UriFactory.java create mode 100644 app/src/main/res/drawable-hdpi/navigation_skip.png create mode 100644 app/src/main/res/drawable-mdpi/navigation_skip.png create mode 100644 app/src/main/res/drawable-xhdpi/navigation_skip.png create mode 100644 app/src/main/res/drawable-xxhdpi/navigation_skip.png create mode 100644 app/src/main/res/layout-sw720dp/setup_task_lists_heading.xml create mode 100644 app/src/main/res/layout/setup_install_apps.xml rename app/src/main/res/{xml/sync_notes.xml => menu/only_skip.xml} (50%) diff --git a/app/src/androidTest/res/setup_task_lists_heading.xml b/app/src/androidTest/res/setup_task_lists_heading.xml new file mode 100644 index 00000000..d4c0683b --- /dev/null +++ b/app/src/androidTest/res/setup_task_lists_heading.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 80df7b32..c8872b9f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,7 +9,7 @@ - - - - - + + - - - - - - diff --git a/app/src/main/java/at/bitfire/davdroid/Constants.java b/app/src/main/java/at/bitfire/davdroid/Constants.java index 7bb0cb6d..5c6d6400 100644 --- a/app/src/main/java/at/bitfire/davdroid/Constants.java +++ b/app/src/main/java/at/bitfire/davdroid/Constants.java @@ -11,7 +11,7 @@ import net.fortuna.ical4j.model.property.ProdId; public class Constants { public static final String - APP_VERSION = "0.7.7", + APP_VERSION = "0.8.0-beta1", ACCOUNT_TYPE = "bitfire.at.davdroid", WEB_URL_HELP = "https://davdroid.bitfire.at/configuration?pk_campaign=davdroid-app", WEB_URL_VIEW_LOGS = "https://github.com/bitfireAT/davdroid/wiki/How-to-view-the-logs"; diff --git a/app/src/main/java/at/bitfire/davdroid/DAVUtils.java b/app/src/main/java/at/bitfire/davdroid/DAVUtils.java new file mode 100644 index 00000000..eeeeaab9 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/DAVUtils.java @@ -0,0 +1,20 @@ +package at.bitfire.davdroid; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DAVUtils { + public static int CalDAVtoARGBColor(String davColor) { + int color = 0xFFC3EA6E; // fallback: "DAVdroid green" + if (davColor != null) { + Pattern p = Pattern.compile("#?(\\p{XDigit}{6})(\\p{XDigit}{2})?"); + Matcher m = p.matcher(davColor); + 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; + } + } + return color; + } +} diff --git a/app/src/main/java/at/bitfire/davdroid/DateUtils.java b/app/src/main/java/at/bitfire/davdroid/DateUtils.java index 94309c84..8f060c60 100644 --- a/app/src/main/java/at/bitfire/davdroid/DateUtils.java +++ b/app/src/main/java/at/bitfire/davdroid/DateUtils.java @@ -11,6 +11,10 @@ package at.bitfire.davdroid; import android.text.format.Time; import android.util.Log; +import net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory; +import net.fortuna.ical4j.model.TimeZone; +import net.fortuna.ical4j.model.TimeZoneRegistry; + import org.apache.commons.lang.StringUtils; import java.util.SimpleTimeZone; @@ -18,13 +22,16 @@ import java.util.SimpleTimeZone; public class DateUtils { private final static String TAG = "davdroid.DateUtils"; + private final static TimeZoneRegistry tzRegistry = new DefaultTimeZoneRegistryFactory().createRegistry(); + + public static String findAndroidTimezoneID(String tzID) { String localTZ = null; String availableTZs[] = SimpleTimeZone.getAvailableIDs(); // first, try to find an exact match (case insensitive) for (String availableTZ : availableTZs) - if (tzID.equalsIgnoreCase(availableTZ)) { + if (availableTZ.equalsIgnoreCase(tzID)) { localTZ = availableTZ; break; } @@ -48,4 +55,9 @@ public class DateUtils { Log.d(TAG, "Assuming time zone " + localTZ + " for " + tzID); return localTZ; } + + public static TimeZone getTimeZone(String tzID) { + return tzRegistry.getTimeZone(tzID); + } + } diff --git a/app/src/main/java/at/bitfire/davdroid/resource/CalDavNotebook.java b/app/src/main/java/at/bitfire/davdroid/resource/CalDavNotebook.java deleted file mode 100644 index 45623324..00000000 --- a/app/src/main/java/at/bitfire/davdroid/resource/CalDavNotebook.java +++ /dev/null @@ -1,72 +0,0 @@ -package at.bitfire.davdroid.resource; - -import android.util.Log; - -import org.apache.http.impl.client.CloseableHttpClient; -import org.simpleframework.xml.Serializer; -import org.simpleframework.xml.core.Persister; - -import java.io.StringWriter; -import java.net.URISyntaxException; - -import at.bitfire.davdroid.webdav.DavCalendarQuery; -import at.bitfire.davdroid.webdav.DavCompFilter; -import at.bitfire.davdroid.webdav.DavFilter; -import at.bitfire.davdroid.webdav.DavMultiget; -import at.bitfire.davdroid.webdav.DavProp; - -public class CalDavNotebook extends RemoteCollection { - private final static String TAG = "davdroid.CalDAVNotebook"; - - public CalDavNotebook(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException { - super(httpClient, baseURL, user, password, preemptiveAuth); - } - - @Override - protected String memberAcceptedMimeTypes() - { - return "text/calendar"; - } - - @Override - protected DavMultiget.Type multiGetType() { - return DavMultiget.Type.CALENDAR; - } - - @Override - protected Note newResourceSkeleton(String name, String ETag) { - return new Note(name, ETag); - } - - - @Override - public String getMemberETagsQuery() { - DavCalendarQuery query = new DavCalendarQuery(); - - // prop - DavProp prop = new DavProp(); - prop.setGetetag(new DavProp.GetETag()); - query.setProp(prop); - - // filter - DavFilter filter = new DavFilter(); - query.setFilter(filter); - - DavCompFilter compFilter = new DavCompFilter("VCALENDAR"); - filter.setCompFilter(compFilter); - - compFilter.setCompFilter(new DavCompFilter("VJOURNAL")); - - Serializer serializer = new Persister(); - StringWriter writer = new StringWriter(); - try { - serializer.write(query, writer); - } catch (Exception e) { - Log.e(TAG, "Couldn't prepare REPORT query", e); - return null; - } - - return writer.toString(); - } - -} diff --git a/app/src/main/java/at/bitfire/davdroid/resource/DavResourceFinder.java b/app/src/main/java/at/bitfire/davdroid/resource/DavResourceFinder.java index 78790b0e..73be34c4 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/DavResourceFinder.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/DavResourceFinder.java @@ -9,6 +9,7 @@ package at.bitfire.davdroid.resource; import android.content.Context; import android.util.Log; +import android.widget.Toast; import org.apache.http.HttpException; import org.apache.http.impl.client.CloseableHttpClient; @@ -127,7 +128,9 @@ public class DavResourceFinder implements Closeable { if (homeSetCalendars.getMembers() != null) possibleCalendars.addAll(homeSetCalendars.getMembers()); - List calendars = new LinkedList<>(); + List + calendars = new LinkedList<>(), + todoLists = new LinkedList<>(); for (WebDavResource resource : possibleCalendars) if (resource.isCalendar()) { Log.i(TAG, "Found calendar: " + resource.getLocation().getPath()); @@ -140,36 +143,41 @@ public class DavResourceFinder implements Closeable { ); info.setTimezone(resource.getTimezone()); + boolean isCalendar = false, + isTodoList = false; if (resource.getSupportedComponents() == null) { // no info about supported components, assuming all components are supported - info.setSupportingEvents(true); - info.setSupportingNotes(true); + isCalendar = true; + isTodoList = true; } else { // CALDAV:supported-calendar-component-set available for (String supportedComponent : resource.getSupportedComponents()) if ("VEVENT".equalsIgnoreCase(supportedComponent)) - info.setSupportingEvents(true); - else if ("VJOURNAL".equalsIgnoreCase(supportedComponent)) - info.setSupportingNotes(true); + isCalendar = true; else if ("VTODO".equalsIgnoreCase(supportedComponent)) - info.setSupportingTasks(true); + isTodoList = true; - if (!info.isSupportingEvents() && !info.isSupportingNotes() && !info.isSupportingTasks()) { - Log.i(TAG, "Ignoring this calendar because it supports neither VEVENT nor VJOURNAL nor VTODO"); + if (!isCalendar && !isTodoList) { + Log.i(TAG, "Ignoring this calendar because it supports neither VEVENT nor VTODO"); continue; } } - calendars.add(info); + // use a copy constructor to allow different "enabled" status for calendars and todo lists + if (isCalendar) + calendars.add(new ServerInfo.ResourceInfo(info)); + if (isTodoList) + todoLists.add(new ServerInfo.ResourceInfo(info)); } + serverInfo.setCalendars(calendars); + serverInfo.setTodoLists(todoLists); } 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)); - } diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java index b058c0aa..c389af11 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.java @@ -658,8 +658,7 @@ public class LocalAddressBook extends LocalCollection { Contact contact = (Contact)resource; if (!update) - builder = builder - .withValue(RawContacts.ACCOUNT_NAME, account.name) + builder .withValue(RawContacts.ACCOUNT_NAME, account.name) .withValue(RawContacts.ACCOUNT_TYPE, account.type); return builder @@ -800,14 +799,13 @@ public class LocalAddressBook extends LocalCollection { 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); + 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); + builder.withValue(Phone.LABEL, typeLabel); return builder; } @@ -834,14 +832,13 @@ public class LocalAddressBook extends LocalCollection { } } - builder = builder - .withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE) + 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); + builder.withValue(Email.LABEL, typeLabel); return builder; } @@ -928,22 +925,20 @@ public class LocalAddressBook extends LocalCollection { if (sipAddress) // save as SIP address - builder = builder - .withValue(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE) - .withValue(Im.DATA, impp.getHandle()) - .withValue(Im.TYPE, typeCode); + 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); + 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); + builder.withValue(Im.CUSTOM_PROTOCOL, protocolLabel); } if (typeLabel != null) - builder = builder.withValue(Im.LABEL, typeLabel); + builder.withValue(Im.LABEL, typeLabel); return builder; } @@ -996,19 +991,18 @@ public class LocalAddressBook extends LocalCollection { 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()); + 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); + builder.withValue(StructuredPostal.LABEL, typeLabel); return builder; } diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.java b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.java index 5212cfe5..66f45581 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.java @@ -75,6 +75,7 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import at.bitfire.davdroid.DAVUtils; import at.bitfire.davdroid.DateUtils; import lombok.Cleanup; import lombok.Getter; @@ -120,24 +121,13 @@ public class LocalCalendar extends LocalCollection { final 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.CALENDAR_COLOR, DAVUtils.CalDAVtoARGBColor(info.getColor())); values.put(Calendars.OWNER_ACCOUNT, account.name); values.put(Calendars.SYNC_EVENTS, 1); values.put(Calendars.VISIBLE, 1); @@ -527,12 +517,10 @@ public class LocalCalendar extends LocalCollection { final Event event = (Event)resource; if (!update) - builder = builder - .withValue(Events.ACCOUNT_TYPE, account.type) + builder .withValue(Events.ACCOUNT_TYPE, account.type) .withValue(Events.ACCOUNT_NAME, account.name); - builder = builder - .withValue(Events.CALENDAR_ID, id) + builder .withValue(Events.CALENDAR_ID, id) .withValue(Events.ALL_DAY, event.isAllDay() ? 1 : 0) .withValue(Events.DTSTART, event.getDtStartInMillis()) .withValue(Events.EVENT_TIMEZONE, event.getDtStartTzID()) @@ -545,12 +533,11 @@ public class LocalCalendar extends LocalCollection { final RecurrenceId recurrenceId = event.getRecurrenceId(); if (recurrenceId == null) { // this event is a "master event" (not an exception) - builder = builder - .withValue(entryColumnRemoteName(), event.getName()) + builder .withValue(entryColumnRemoteName(), event.getName()) .withValue(entryColumnETag(), event.getETag()) .withValue(entryColumnUID(), event.getUid()); } else { - builder = builder.withValue(Events.ORIGINAL_SYNC_ID, event.getName()); + builder.withValue(Events.ORIGINAL_SYNC_ID, event.getName()); // ORIGINAL_INSTANCE_TIME and ORIGINAL_ALL_DAY is set in buildExceptions. // It's not possible to use only the RECURRENCE-ID to calculate @@ -561,40 +548,38 @@ public class LocalCalendar extends LocalCollection { boolean recurring = false; if (event.getRrule() != null) { recurring = true; - builder = builder.withValue(Events.RRULE, event.getRrule().getValue()); + builder.withValue(Events.RRULE, event.getRrule().getValue()); } if (!event.getRdates().isEmpty()) { recurring = true; - builder = builder.withValue(Events.RDATE, recurrenceSetsToAndroidString(event.getRdates())); + builder.withValue(Events.RDATE, recurrenceSetsToAndroidString(event.getRdates())); } if (event.getExrule() != null) - builder = builder.withValue(Events.EXRULE, event.getExrule().getValue()); + builder.withValue(Events.EXRULE, event.getExrule().getValue()); if (!event.getExdates().isEmpty()) - builder = builder.withValue(Events.EXDATE, recurrenceSetsToAndroidString(event.getExdates())); + builder.withValue(Events.EXDATE, recurrenceSetsToAndroidString(event.getExdates())); // 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()) + builder.withValue(Events.DURATION, duration.getValue()); + } else + builder .withValue(Events.DTEND, event.getDtEndInMillis()) .withValue(Events.EVENT_END_TIMEZONE, event.getDtEndTzID()); - } - + if (event.getSummary() != null) - builder = builder.withValue(Events.TITLE, event.getSummary()); + builder.withValue(Events.TITLE, event.getSummary()); if (event.getLocation() != null) - builder = builder.withValue(Events.EVENT_LOCATION, event.getLocation()); + builder.withValue(Events.EVENT_LOCATION, event.getLocation()); if (event.getDescription() != null) - builder = builder.withValue(Events.DESCRIPTION, event.getDescription()); + 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()); + builder.withValue(Events.ORGANIZER, organizer.getSchemeSpecificPart()); } Status status = event.getStatus(); @@ -604,13 +589,13 @@ public class LocalCalendar extends LocalCollection { statusCode = Events.STATUS_CONFIRMED; else if (status == Status.VEVENT_CANCELLED) statusCode = Events.STATUS_CANCELED; - builder = builder.withValue(Events.STATUS, statusCode); + builder.withValue(Events.STATUS, statusCode); } - builder = builder.withValue(Events.AVAILABILITY, event.isOpaque() ? Events.AVAILABILITY_BUSY : Events.AVAILABILITY_FREE); + 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); + builder.withValue(Events.ACCESS_LEVEL, event.getForPublic() ? Events.ACCESS_PUBLIC : Events.ACCESS_PRIVATE); return builder; } @@ -683,7 +668,7 @@ public class LocalCalendar extends LocalCollection { final Cn cn = (Cn)attendee.getParameter(Parameter.CN); if (cn != null) - builder = builder.withValue(Attendees.ATTENDEE_NAME, cn.getValue()); + builder.withValue(Attendees.ATTENDEE_NAME, cn.getValue()); int type = Attendees.TYPE_NONE; @@ -702,7 +687,7 @@ public class LocalCalendar extends LocalCollection { else if (role == Role.REQ_PARTICIPANT) type = Attendees.TYPE_REQUIRED; } - builder = builder.withValue(Attendees.ATTENDEE_RELATIONSHIP, relationship); + builder.withValue(Attendees.ATTENDEE_RELATIONSHIP, relationship); } int status = Attendees.ATTENDEE_STATUS_NONE; diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalNotebook.java b/app/src/main/java/at/bitfire/davdroid/resource/LocalNotebook.java deleted file mode 100644 index e8f915fc..00000000 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalNotebook.java +++ /dev/null @@ -1,179 +0,0 @@ -package at.bitfire.davdroid.resource; - -import android.accounts.Account; -import android.content.ContentProviderClient; -import android.content.ContentProviderOperation; -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.RemoteException; -import android.provider.CalendarContract; -import android.util.Log; - -import net.fortuna.ical4j.model.DateTime; -import net.fortuna.ical4j.model.property.Created; -import net.fortuna.ical4j.model.property.DtStamp; - -import org.apache.commons.lang.StringUtils; - -import java.util.LinkedList; -import java.util.List; - -import at.bitfire.davdroid.Constants; -import at.bitfire.notebooks.provider.NoteContract; -import lombok.Cleanup; -import lombok.Getter; - -public class LocalNotebook extends LocalCollection { - private final static String TAG = "davdroid.LocalNotebook"; - - @Getter protected final String url; - @Getter protected final long id; - - protected static String COLLECTION_COLUMN_CTAG = NoteContract.Notebooks.SYNC1; - - @Override protected Uri entriesURI() { return syncAdapterURI(NoteContract.Notes.CONTENT_URI); } - @Override protected String entryColumnAccountType() { return NoteContract.Notes.ACCOUNT_TYPE; } - @Override protected String entryColumnAccountName() { return NoteContract.Notes.ACCOUNT_NAME; } - @Override protected String entryColumnParentID() { return NoteContract.Notes.NOTEBOOK_ID; } - @Override protected String entryColumnID() { return NoteContract.Notes._ID; } - @Override protected String entryColumnRemoteName() { return NoteContract.Notes._SYNC_ID; } - @Override protected String entryColumnETag() { return NoteContract.Notes.SYNC1; } - @Override protected String entryColumnDirty() { return NoteContract.Notes.DIRTY; } - @Override protected String entryColumnDeleted() { return NoteContract.Notes.DELETED; } - @Override protected String entryColumnUID() { return NoteContract.Notes.UID; } - - - public static Uri create(Account account, ContentResolver resolver, ServerInfo.ResourceInfo info) throws LocalStorageException { - final ContentProviderClient client = resolver.acquireContentProviderClient(NoteContract.AUTHORITY); - if (client == null) - throw new LocalStorageException("No notes provider found"); - - ContentValues values = new ContentValues(); - values.put(NoteContract.Notebooks._SYNC_ID, info.getURL()); - values.put(NoteContract.Notebooks.NAME, info.getTitle()); - - Log.i(TAG, "Inserting notebook: " + values.toString()); - try { - return client.insert(notebooksURI(account), values); - } catch (RemoteException e) { - throw new LocalStorageException(e); - } - } - - public static LocalNotebook[] findAll(Account account, ContentProviderClient providerClient) throws RemoteException { - @Cleanup Cursor cursor = providerClient.query(notebooksURI(account), - new String[] { NoteContract.Notebooks._ID, NoteContract.Notebooks._SYNC_ID }, - NoteContract.Notebooks.DELETED + "=0", null, null); - - LinkedList notebooks = new LinkedList<>(); - while (cursor != null && cursor.moveToNext()) - notebooks.add(new LocalNotebook(account, providerClient, cursor.getInt(0), cursor.getString(1))); - return notebooks.toArray(new LocalNotebook[0]); - } - - public LocalNotebook(Account account, ContentProviderClient providerClient, long id, String url) throws RemoteException { - super(account, providerClient); - this.id = id; - this.url = url; - - } - - - @Override - public String getCTag() throws LocalStorageException { - try { - @Cleanup Cursor c = providerClient.query(ContentUris.withAppendedId(notebooksURI(account), id), - new String[] { COLLECTION_COLUMN_CTAG }, null, null, null); - if (c != null && c.moveToFirst()) - return c.getString(0); - else - throw new LocalStorageException("Couldn't query notebook 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(notebooksURI(account), id), values, null, null); - } catch(RemoteException e) { - throw new LocalStorageException(e); - } - } - - @Override - public Note newResource(long localID, String resourceName, String eTag) { - return new Note(localID, resourceName, eTag); - } - - - @Override - public void populate(Resource record) throws LocalStorageException { - try { - @Cleanup final Cursor cursor = providerClient.query(entriesURI(), - new String[] { - /* 0 */ entryColumnUID(), NoteContract.Notes.CREATED_AT, NoteContract.Notes.UPDATED_AT, NoteContract.Notes.DTSTART, - /* 4 */ NoteContract.Notes.SUMMARY, NoteContract.Notes.DESCRIPTION, NoteContract.Notes.COMMENT, - /* 7 */ NoteContract.Notes.ORGANIZER, NoteContract.Notes.STATUS, NoteContract.Notes.CLASSIFICATION, - /* 10 */ NoteContract.Notes.CONTACT, NoteContract.Notes.URL - }, entryColumnID() + "=?", new String[]{ String.valueOf(record.getLocalID()) }, null); - - Note note = (Note)record; - if (cursor != null && cursor.moveToFirst()) { - note.setUid(cursor.getString(0)); - - if (!cursor.isNull(1)) - note.setCreated(new Created(new DateTime(cursor.getLong(1)))); - - note.setSummary(cursor.getString(4)); - note.setDescription(cursor.getString(5)); - } - - } catch (RemoteException e) { - throw new LocalStorageException("Couldn't process locally stored note", e); - } - } - - @Override - protected ContentProviderOperation.Builder buildEntry(ContentProviderOperation.Builder builder, Resource resource, boolean update) { - final Note note = (Note)resource; - builder = builder - .withValue(entryColumnParentID(), id) - .withValue(entryColumnRemoteName(), note.getName()) - .withValue(entryColumnUID(), note.getUid()) - .withValue(entryColumnETag(), note.getETag()) - .withValue(NoteContract.Notes.SUMMARY, note.getSummary()) - .withValue(NoteContract.Notes.DESCRIPTION, note.getDescription()); - - if (note.getCreated() != null) - builder = builder.withValue(NoteContract.Notes.CREATED_AT, note.getCreated().getDateTime().getTime()); - - return builder; - } - - @Override - protected void addDataRows(Resource resource, long localID, int backrefIdx) { - } - - @Override - protected void removeDataRows(Resource resource) { - } - - - // helpers - - protected static Uri notebooksURI(Account account) { - return NoteContract.Notebooks.CONTENT_URI.buildUpon() - .appendQueryParameter(NoteContract.Notebooks.ACCOUNT_TYPE, account.type) - .appendQueryParameter(NoteContract.Notebooks.ACCOUNT_NAME, account.name) - .appendQueryParameter(NoteContract.CALLER_IS_SYNCADAPTER, "true") - .build(); - } -} diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.java b/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.java index dd883279..11f98433 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.java @@ -6,6 +6,7 @@ import android.content.ContentProviderOperation; 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.RemoteException; @@ -13,15 +14,25 @@ import android.util.Log; import net.fortuna.ical4j.model.Date; import net.fortuna.ical4j.model.DateTime; +import net.fortuna.ical4j.model.Dur; +import net.fortuna.ical4j.model.TimeZone; +import net.fortuna.ical4j.model.TimeZoneRegistry; import net.fortuna.ical4j.model.property.Clazz; import net.fortuna.ical4j.model.property.Completed; +import net.fortuna.ical4j.model.property.Created; import net.fortuna.ical4j.model.property.DtStart; +import net.fortuna.ical4j.model.property.Due; +import net.fortuna.ical4j.model.property.Duration; import net.fortuna.ical4j.model.property.Status; +import net.fortuna.ical4j.util.TimeZones; +import org.apache.commons.lang.StringUtils; import org.dmfs.provider.tasks.TaskContract; import java.util.LinkedList; +import at.bitfire.davdroid.DAVUtils; +import at.bitfire.davdroid.DateUtils; import lombok.Cleanup; import lombok.Getter; @@ -31,9 +42,11 @@ public class LocalTaskList extends LocalCollection { @Getter protected String url; @Getter protected long id; + public static final String TASKS_AUTHORITY = "org.dmfs.tasks"; + protected static String COLLECTION_COLUMN_CTAG = TaskContract.TaskLists.SYNC1; - @Override protected Uri entriesURI() { return syncAdapterURI(TaskContract.Tasks.CONTENT_URI); } + @Override protected Uri entriesURI() { return syncAdapterURI(TaskContract.Tasks.getContentUri(TASKS_AUTHORITY)); } @Override protected String entryColumnAccountType() { return TaskContract.Tasks.ACCOUNT_TYPE; } @Override protected String entryColumnAccountName() { return TaskContract.Tasks.ACCOUNT_NAME; } @Override protected String entryColumnParentID() { return TaskContract.Tasks.LIST_ID; } @@ -46,15 +59,16 @@ public class LocalTaskList extends LocalCollection { public static Uri create(Account account, ContentResolver resolver, ServerInfo.ResourceInfo info) throws LocalStorageException { - final ContentProviderClient client = resolver.acquireContentProviderClient(TaskContract.AUTHORITY); + final ContentProviderClient client = resolver.acquireContentProviderClient(TASKS_AUTHORITY); if (client == null) throw new LocalStorageException("No tasks provider found"); ContentValues values = new ContentValues(); values.put(TaskContract.TaskLists.ACCOUNT_NAME, account.name); - values.put(TaskContract.TaskLists.ACCOUNT_TYPE, /*account.type*/"davdroid.new"); + values.put(TaskContract.TaskLists.ACCOUNT_TYPE, account.type); values.put(TaskContract.TaskLists._SYNC_ID, info.getURL()); values.put(TaskContract.TaskLists.LIST_NAME, info.getTitle()); + values.put(TaskContract.TaskLists.LIST_COLOR, DAVUtils.CalDAVtoARGBColor(info.getColor())); values.put(TaskContract.TaskLists.OWNER, account.name); values.put(TaskContract.TaskLists.ACCESS_LEVEL, 0); values.put(TaskContract.TaskLists.SYNC_ENABLED, 1); @@ -122,19 +136,36 @@ public class LocalTaskList extends LocalCollection { try { @Cleanup final Cursor cursor = providerClient.query(entriesURI(), new String[] { - /* 0 */ entryColumnUID(), TaskContract.Tasks.TITLE, TaskContract.Tasks.LOCATION, TaskContract.Tasks.DESCRIPTION, TaskContract.Tasks.URL, - /* 5 */ TaskContract.Tasks.CLASSIFICATION, TaskContract.Tasks.STATUS, TaskContract.Tasks.PERCENT_COMPLETE, - /* 8 */ TaskContract.Tasks.DTSTART, TaskContract.Tasks.IS_ALLDAY, /*TaskContract.Tasks.COMPLETED, TaskContract.Tasks.COMPLETED_IS_ALLDAY*/ + /* 0 */ entryColumnUID(), TaskContract.Tasks.TITLE, TaskContract.Tasks.LOCATION, TaskContract.Tasks.DESCRIPTION, TaskContract.Tasks.URL, + /* 5 */ TaskContract.Tasks.CLASSIFICATION, TaskContract.Tasks.STATUS, TaskContract.Tasks.PERCENT_COMPLETE, + /* 8 */ TaskContract.Tasks.TZ, TaskContract.Tasks.DTSTART, TaskContract.Tasks.IS_ALLDAY, + /* 11 */ TaskContract.Tasks.DUE, TaskContract.Tasks.DURATION, TaskContract.Tasks.COMPLETED, + /* 14 */ TaskContract.Tasks.CREATED, TaskContract.Tasks.LAST_MODIFIED, TaskContract.Tasks.PRIORITY }, entryColumnID() + "=?", new String[]{ String.valueOf(record.getLocalID()) }, null); Task task = (Task)record; if (cursor != null && cursor.moveToFirst()) { task.setUid(cursor.getString(0)); - task.setSummary(cursor.getString(1)); - task.setLocation(cursor.getString(2)); - task.setDescription(cursor.getString(3)); - task.setUrl(cursor.getString(4)); + if (!cursor.isNull(14)) + task.setCreatedAt(new DateTime(cursor.getLong(14))); + if (!cursor.isNull(15)) + task.setLastModified(new DateTime(cursor.getLong(15))); + + if (!StringUtils.isEmpty(cursor.getString(1))) + task.setSummary(cursor.getString(1)); + + if (!StringUtils.isEmpty(cursor.getString(2))) + task.setLocation(cursor.getString(2)); + + if (!StringUtils.isEmpty(cursor.getString(3))) + task.setDescription(cursor.getString(3)); + + if (!StringUtils.isEmpty(cursor.getString(4))) + task.setUrl(cursor.getString(4)); + + if (!cursor.isNull(16)) + task.setPriority(cursor.getInt(16)); if (!cursor.isNull(5)) switch (cursor.getInt(5)) { @@ -165,17 +196,37 @@ public class LocalTaskList extends LocalCollection { if (!cursor.isNull(7)) task.setPercentComplete(cursor.getInt(7)); - if (!cursor.isNull(8) && !cursor.isNull(9)) { - long ts = cursor.getLong(8); - boolean allDay = cursor.getInt(9) != 0; - task.setDtStart(new DtStart(allDay ? new Date(ts) : new DateTime(ts))); + TimeZone tz = null; + if (!cursor.isNull(8)) + tz = DateUtils.getTimeZone(cursor.getString(8)); + + if (!cursor.isNull(9) && !cursor.isNull(10)) { + long ts = cursor.getLong(9); + boolean allDay = cursor.getInt(10) != 0; + + Date dt; + if (allDay) + dt = new Date(ts); + else { + dt = new DateTime(ts); + if (tz != null) + ((DateTime)dt).setTimeZone(tz); + } + task.setDtStart(new DtStart(dt)); } - /*if (!cursor.isNull(10) && !cursor.isNull(11)) { - long ts = cursor.getLong(10); - // boolean allDay = cursor.getInt(11) != 0; - task.setCompletedAt(new Completed(allDay ? new Date(ts) : new DateTime(ts))); - }*/ + if (!cursor.isNull(11)) { + DateTime dt = new DateTime(cursor.getLong(11)); + if (tz != null) + dt.setTimeZone(tz); + task.setDue(new Due(dt)); + } + + if (!cursor.isNull(12)) + task.setDuration(new Duration(new Dur(cursor.getString(12)))); + + if (!cursor.isNull(13)) + task.setCompletedAt(new Completed(new DateTime(cursor.getLong(13)))); } } catch (RemoteException e) { @@ -188,17 +239,21 @@ public class LocalTaskList extends LocalCollection { final Task task = (Task)resource; if (!update) - builder = builder - .withValue(entryColumnParentID(), id) + builder .withValue(entryColumnParentID(), id) .withValue(entryColumnRemoteName(), task.getName()); - builder = builder - .withValue(entryColumnUID(), task.getUid()) + builder.withValue(entryColumnUID(), task.getUid()) .withValue(entryColumnETag(), task.getETag()) .withValue(TaskContract.Tasks.TITLE, task.getSummary()) .withValue(TaskContract.Tasks.LOCATION, task.getLocation()) .withValue(TaskContract.Tasks.DESCRIPTION, task.getDescription()) - .withValue(TaskContract.Tasks.URL, task.getUrl()); + .withValue(TaskContract.Tasks.URL, task.getUrl()) + .withValue(TaskContract.Tasks.PRIORITY, task.getPriority()); + + if (task.getCreatedAt() != null) + builder.withValue(TaskContract.Tasks.CREATED, task.getCreatedAt().getTime()); + if (task.getLastModified() != null) + builder.withValue(TaskContract.Tasks.LAST_MODIFIED, task.getLastModified().getTime()); if (task.getClassification() != null) { int classCode = TaskContract.Tasks.CLASSIFICATION_PRIVATE; @@ -220,40 +275,54 @@ public class LocalTaskList extends LocalCollection { else if (task.getStatus() == Status.VTODO_CANCELLED) statusCode = TaskContract.Tasks.STATUS_CANCELLED; } - builder = builder - .withValue(TaskContract.Tasks.STATUS, statusCode) + builder .withValue(TaskContract.Tasks.STATUS, statusCode) .withValue(TaskContract.Tasks.PERCENT_COMPLETE, task.getPercentComplete()); - /*if (task.getCreatedAt() != null) - builder = builder.withValue(TaskContract.Tasks.CREATED, task.getCreatedAt().getDate().getTime());/* + TimeZone tz = null; if (task.getDtStart() != null) { Date start = task.getDtStart().getDate(); boolean allDay; - if (start instanceof DateTime) + if (start instanceof DateTime) { allDay = false; - else { - task.getDtStart().setUtc(true); + tz = ((DateTime)start).getTimeZone(); + } else allDay = true; - } long ts = start.getTime(); - builder = builder.withValue(TaskContract.Tasks.DTSTART, ts); - builder = builder.withValue(TaskContract.Tasks.IS_ALLDAY, allDay ? 1 : 0); + builder .withValue(TaskContract.Tasks.DTSTART, ts) + .withValue(TaskContract.Tasks.IS_ALLDAY, allDay ? 1 : 0); } - /*if (task.getCompletedAt() != null) { + if (task.getDue() != null) { + Due due = task.getDue(); + builder.withValue(TaskContract.Tasks.DUE, due.getDate().getTime()); + if (tz == null) + tz = due.getTimeZone(); + + } else if (task.getDuration() != null) + builder.withValue(TaskContract.Tasks.DURATION, task.getDuration().getValue()); + + if (task.getCompletedAt() != null) { Date completed = task.getCompletedAt().getDate(); boolean allDay; - if (completed instanceof DateTime) + if (completed instanceof DateTime) { allDay = false; - else { + if (tz == null) + tz = ((DateTime)completed).getTimeZone(); + } else { task.getCompletedAt().setUtc(true); allDay = true; } long ts = completed.getTime(); - builder = builder.withValue(TaskContract.Tasks.COMPLETED, ts); - builder = builder.withValue(TaskContract.Tasks.COMPLETED_IS_ALLDAY, allDay ? 1 : 0); - }*/ + builder .withValue(TaskContract.Tasks.COMPLETED, ts) + .withValue(TaskContract.Tasks.COMPLETED_IS_ALLDAY, allDay ? 1 : 0); + } + + // TZ *must* be provided when DTSTART or DUE is set + if ((task.getDtStart() != null || task.getDue() != null) && tz == null) + tz = DateUtils.getTimeZone(TimeZones.GMT_ID); + if (tz != null) + builder.withValue(TaskContract.Tasks.TZ, DateUtils.findAndroidTimezoneID(tz.getID())); return builder; } @@ -269,18 +338,28 @@ public class LocalTaskList extends LocalCollection { // helpers + public static boolean isAvailable(Context context) { + try { + @Cleanup("release") ContentProviderClient client = context.getContentResolver().acquireContentProviderClient(TASKS_AUTHORITY); + return client != null; + } catch (SecurityException e) { + Log.e(TAG, "DAVdroid is not allowed to access tasks", e); + return false; + } + } + @Override protected Uri syncAdapterURI(Uri baseURI) { return baseURI.buildUpon() - .appendQueryParameter(entryColumnAccountType(), /*account.type*/"davdroid.new") + .appendQueryParameter(entryColumnAccountType(), account.type) .appendQueryParameter(entryColumnAccountName(), account.name) .appendQueryParameter(TaskContract.CALLER_IS_SYNCADAPTER, "true") .build(); } protected static Uri taskListsURI(Account account) { - return TaskContract.TaskLists.CONTENT_URI.buildUpon() - .appendQueryParameter(TaskContract.TaskLists.ACCOUNT_TYPE, /*account.type*/"davdroid.new") + return TaskContract.TaskLists.getContentUri(TASKS_AUTHORITY).buildUpon() + .appendQueryParameter(TaskContract.TaskLists.ACCOUNT_TYPE, account.type) .appendQueryParameter(TaskContract.TaskLists.ACCOUNT_NAME, account.name) .appendQueryParameter(TaskContract.CALLER_IS_SYNCADAPTER, "true") .build(); diff --git a/app/src/main/java/at/bitfire/davdroid/resource/ServerInfo.java b/app/src/main/java/at/bitfire/davdroid/resource/ServerInfo.java index 207c9515..fdf81565 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/ServerInfo.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/ServerInfo.java @@ -19,9 +19,7 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor(suppressConstructorProperties=true) @Data -public class ServerInfo implements Serializable { - private static final long serialVersionUID = 6744847358282980437L; - +public class ServerInfo { enum Scheme { HTTP, HTTPS, MAILTO } @@ -34,8 +32,9 @@ public class ServerInfo implements Serializable { private boolean calDAV = false, cardDAV = false; private List - addressBooks = new LinkedList(), - calendars = new LinkedList(); + addressBooks = new LinkedList<>(), + calendars = new LinkedList<>(), + todoLists = new LinkedList<>(); public boolean hasEnabledCalendars() { @@ -48,9 +47,7 @@ public class ServerInfo implements Serializable { @RequiredArgsConstructor(suppressConstructorProperties=true) @Data - public static class ResourceInfo implements Serializable { - private static final long serialVersionUID = -5516934508229552112L; - + public static class ResourceInfo { public enum Type { ADDRESS_BOOK, CALENDAR @@ -69,9 +66,25 @@ public class ServerInfo implements Serializable { VCardVersion vCardVersion; String timezone; - boolean supportingEvents = false, - supportingNotes = false, - supportingTasks = false; + + + // copy constructor + public ResourceInfo(ResourceInfo src) { + enabled = src.enabled; + type = src.type; + readOnly = src.readOnly; + + URL = src.URL; + title = src.title; + description = src.description; + color = src.color; + + vCardVersion = src.vCardVersion; + timezone = src.timezone; + } + + + // some logic public String getTitle() { if (title == null) { diff --git a/app/src/main/java/at/bitfire/davdroid/resource/Task.java b/app/src/main/java/at/bitfire/davdroid/resource/Task.java index 47aa6e3b..e9b853f9 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/Task.java +++ b/app/src/main/java/at/bitfire/davdroid/resource/Task.java @@ -7,6 +7,8 @@ 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.PropertyList; import net.fortuna.ical4j.model.ValidationException; import net.fortuna.ical4j.model.component.VToDo; @@ -15,6 +17,9 @@ import net.fortuna.ical4j.model.property.Completed; import net.fortuna.ical4j.model.property.Created; import net.fortuna.ical4j.model.property.Description; import net.fortuna.ical4j.model.property.DtStart; +import net.fortuna.ical4j.model.property.Due; +import net.fortuna.ical4j.model.property.Duration; +import net.fortuna.ical4j.model.property.LastModified; import net.fortuna.ical4j.model.property.Location; import net.fortuna.ical4j.model.property.PercentComplete; import net.fortuna.ical4j.model.property.Priority; @@ -41,13 +46,17 @@ import lombok.Setter; public class Task extends Resource { private final static String TAG = "davdroid.Task"; + @Getter @Setter DateTime createdAt; + @Getter @Setter DateTime lastModified; + @Getter @Setter String summary, location, description, url; @Getter @Setter int priority; @Getter @Setter Clazz classification; @Getter @Setter Status status; - @Getter @Setter Created createdAt; @Getter @Setter DtStart dtStart; + @Getter @Setter Due due; + @Getter @Setter Duration duration; @Getter @Setter Completed completedAt; @Getter @Setter Integer percentComplete; @@ -90,6 +99,11 @@ public class Task extends Resource { if (todo.getUid() != null) uid = todo.getUid().getValue(); + if (todo.getCreated() != null) + createdAt = todo.getCreated().getDateTime(); + if (todo.getLastModified() != null) + lastModified = todo.getLastModified().getDateTime(); + if (todo.getSummary() != null) summary = todo.getSummary().getValue(); if (todo.getLocation() != null) @@ -105,8 +119,10 @@ public class Task extends Resource { if (todo.getStatus() != null) status = todo.getStatus(); - if (todo.getCreated() != null) - createdAt = todo.getCreated(); + if (todo.getDue() != null) + due = todo.getDue(); + if (todo.getDuration() != null) + duration = todo.getDuration(); if (todo.getStartDate() != null) dtStart = todo.getStartDate(); if (todo.getDateCompleted() != null) @@ -134,6 +150,11 @@ public class Task extends Resource { if (uid != null) props.add(new Uid(uid)); + if (createdAt != null) + props.add(new Created(createdAt)); + if (lastModified != null) + props.add(new LastModified(lastModified)); + if (summary != null) props.add(new Summary(summary)); if (location != null) @@ -153,8 +174,10 @@ public class Task extends Resource { if (status != null) props.add(status); - if (createdAt != null) - props.add(createdAt); + if (due != null) + props.add(due); + if (duration != null) + props.add(duration); if (dtStart != null) props.add(dtStart); if (completedAt != null) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountSettings.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountSettings.java index 99e38fdd..af85545f 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountSettings.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountSettings.java @@ -102,12 +102,12 @@ public class AccountSettings { // sync. settings - public Long getContactsSyncInterval() { - if (ContentResolver.getIsSyncable(account, ContactsContract.AUTHORITY) <= 0) + public Long getSyncInterval(String authority) { + if (ContentResolver.getIsSyncable(account, authority) <= 0) return null; - if (ContentResolver.getSyncAutomatically(account, ContactsContract.AUTHORITY)) { - List syncs = ContentResolver.getPeriodicSyncs(account, ContactsContract.AUTHORITY); + if (ContentResolver.getSyncAutomatically(account, authority)) { + List syncs = ContentResolver.getPeriodicSyncs(account, authority); if (syncs.isEmpty()) return SYNC_INTERVAL_MANUALLY; else @@ -116,35 +116,12 @@ public class AccountSettings { return SYNC_INTERVAL_MANUALLY; } - public void setContactsSyncInterval(long seconds) { + public void setSyncInterval(String authority, long seconds) { if (seconds == SYNC_INTERVAL_MANUALLY) { - ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, false); + ContentResolver.setSyncAutomatically(account, authority, false); } else { - ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true); - ContentResolver.addPeriodicSync(account, ContactsContract.AUTHORITY, new Bundle(), seconds); - } - } - - public Long getCalendarsSyncInterval() { - if (ContentResolver.getIsSyncable(account, CalendarContract.AUTHORITY) <= 0) - return null; - - if (ContentResolver.getSyncAutomatically(account, CalendarContract.AUTHORITY)) { - List syncs = ContentResolver.getPeriodicSyncs(account, CalendarContract.AUTHORITY); - if (syncs.isEmpty()) - return SYNC_INTERVAL_MANUALLY; - else - return syncs.get(0).period; - } else - return SYNC_INTERVAL_MANUALLY; - } - - public void setCalendarsSyncInterval(long seconds) { - if (seconds == SYNC_INTERVAL_MANUALLY) { - ContentResolver.setSyncAutomatically(account, CalendarContract.AUTHORITY, false); - } else { - ContentResolver.setSyncAutomatically(account, CalendarContract.AUTHORITY, true); - ContentResolver.addPeriodicSync(account, CalendarContract.AUTHORITY, new Bundle(), seconds); + ContentResolver.setSyncAutomatically(account, authority, true); + ContentResolver.addPeriodicSync(account, authority, new Bundle(), seconds); } } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/NotesSyncAdapterService.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/NotesSyncAdapterService.java deleted file mode 100644 index a26510bb..00000000 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/NotesSyncAdapterService.java +++ /dev/null @@ -1,73 +0,0 @@ -package at.bitfire.davdroid.syncadapter; - -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 java.net.URISyntaxException; -import java.util.HashMap; -import java.util.Map; - -import at.bitfire.davdroid.resource.CalDavNotebook; -import at.bitfire.davdroid.resource.LocalCollection; -import at.bitfire.davdroid.resource.LocalNotebook; -import at.bitfire.davdroid.resource.RemoteCollection; - -public class NotesSyncAdapterService extends Service { - private static NotesSyncAdapter syncAdapter; - - @Override - public void onCreate() { - if (syncAdapter == null) - syncAdapter = new NotesSyncAdapter(getApplicationContext()); - } - - @Override - public void onDestroy() { - syncAdapter.close(); - syncAdapter = null; - } - - @Override - public IBinder onBind(Intent intent) { - return syncAdapter.getSyncAdapterBinder(); - } - - - private static class NotesSyncAdapter extends DavSyncAdapter { - private final static String TAG = "davdroid.NotesSync"; - - private NotesSyncAdapter(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 (LocalNotebook noteList : LocalNotebook.findAll(account, provider)) { - RemoteCollection dav = new CalDavNotebook(httpClient, noteList.getUrl(), userName, password, preemptive); - map.put(noteList, dav); - } - return map; - } catch (RemoteException ex) { - Log.e(TAG, "Couldn't find local notebooks", ex); - } catch (URISyntaxException ex) { - Log.e(TAG, "Couldn't build calendar URI", ex); - } - - return null; - } - } -} diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.java index f70625b9..489b90d9 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.java +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.java @@ -20,9 +20,7 @@ import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; -import at.bitfire.davdroid.resource.CalDavCalendar; import at.bitfire.davdroid.resource.CalDavTaskList; -import at.bitfire.davdroid.resource.LocalCalendar; import at.bitfire.davdroid.resource.LocalCollection; import at.bitfire.davdroid.resource.LocalTaskList; import at.bitfire.davdroid.resource.RemoteCollection; diff --git a/app/src/main/java/at/bitfire/davdroid/ui/settings/AccountFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/settings/AccountFragment.java index e59dc586..1769c523 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/settings/AccountFragment.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/settings/AccountFragment.java @@ -17,8 +17,13 @@ import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceFragment; import android.preference.SwitchPreference; +import android.provider.CalendarContract; +import android.provider.ContactsContract; + +import org.dmfs.provider.tasks.TaskContract; import at.bitfire.davdroid.R; +import at.bitfire.davdroid.resource.LocalTaskList; import at.bitfire.davdroid.syncadapter.AccountSettings; import ezvcard.VCardVersion; import lombok.Setter; @@ -78,7 +83,7 @@ public class AccountFragment extends PreferenceFragment { // category: synchronization final ListPreference prefSyncContacts = (ListPreference)findPreference("sync_interval_contacts"); - final Long syncIntervalContacts = settings.getContactsSyncInterval(); + final Long syncIntervalContacts = settings.getSyncInterval(ContactsContract.AUTHORITY); if (syncIntervalContacts != null) { prefSyncContacts.setValue(syncIntervalContacts.toString()); if (syncIntervalContacts == AccountSettings.SYNC_INTERVAL_MANUALLY) @@ -88,7 +93,7 @@ public class AccountFragment extends PreferenceFragment { prefSyncContacts.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - settings.setContactsSyncInterval(Long.parseLong((String)newValue)); + settings.setSyncInterval(ContactsContract.AUTHORITY, Long.parseLong((String) newValue)); readFromAccount(); return true; } @@ -99,7 +104,7 @@ public class AccountFragment extends PreferenceFragment { } final ListPreference prefSyncCalendars = (ListPreference)findPreference("sync_interval_calendars"); - final Long syncIntervalCalendars = settings.getCalendarsSyncInterval(); + final Long syncIntervalCalendars = settings.getSyncInterval(CalendarContract.AUTHORITY); if (syncIntervalCalendars != null) { prefSyncCalendars.setValue(syncIntervalCalendars.toString()); if (syncIntervalCalendars == AccountSettings.SYNC_INTERVAL_MANUALLY) @@ -109,7 +114,7 @@ public class AccountFragment extends PreferenceFragment { prefSyncCalendars.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - settings.setCalendarsSyncInterval(Long.parseLong((String)newValue)); + settings.setSyncInterval(CalendarContract.AUTHORITY, Long.parseLong((String) newValue)); readFromAccount(); return true; } @@ -119,6 +124,27 @@ public class AccountFragment extends PreferenceFragment { prefSyncCalendars.setSummary(R.string.settings_sync_summary_not_available); } + final ListPreference prefSyncTasks = (ListPreference)findPreference("sync_interval_tasks"); + final Long syncIntervalTasks = settings.getSyncInterval(LocalTaskList.TASKS_AUTHORITY); + if (syncIntervalTasks != null) { + prefSyncTasks.setValue(syncIntervalTasks.toString()); + if (syncIntervalTasks == AccountSettings.SYNC_INTERVAL_MANUALLY) + prefSyncTasks.setSummary(R.string.settings_sync_summary_manually); + else + prefSyncTasks.setSummary(getString(R.string.settings_sync_summary_periodically, syncIntervalTasks / 60)); + prefSyncTasks.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + settings.setSyncInterval(LocalTaskList.TASKS_AUTHORITY, Long.parseLong((String) newValue)); + readFromAccount(); + return true; + } + }); + } else { + prefSyncTasks.setEnabled(false); + prefSyncTasks.setSummary(R.string.settings_sync_summary_not_available); + } + // category: address book final CheckBoxPreference prefVCard4 = (CheckBoxPreference) findPreference("vcard4_support"); if (settings.getAddressBookURL() != null) { // does this account even have an address book? diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.java index 9aeff395..7cc901b1 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.java @@ -16,6 +16,7 @@ import android.provider.CalendarContract; import android.provider.ContactsContract; import android.text.Editable; import android.text.TextWatcher; +import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -26,22 +27,19 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; -import org.dmfs.provider.tasks.TaskContract; +import java.util.List; import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.R; import at.bitfire.davdroid.resource.LocalCalendar; -import at.bitfire.davdroid.resource.LocalNotebook; import at.bitfire.davdroid.resource.LocalStorageException; import at.bitfire.davdroid.resource.LocalTaskList; import at.bitfire.davdroid.resource.ServerInfo; -import at.bitfire.davdroid.resource.Task; import at.bitfire.davdroid.syncadapter.AccountSettings; -import at.bitfire.notebooks.provider.NoteContract; public class AccountDetailsFragment extends Fragment implements TextWatcher { - public static final String KEY_SERVER_INFO = "server_info"; - + public static final String TAG = "davdroid.AccountDetails"; + ServerInfo serverInfo; EditText editAccountName; @@ -51,7 +49,7 @@ public class AccountDetailsFragment extends Fragment implements TextWatcher { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.setup_account_details, container, false); - serverInfo = (ServerInfo)getArguments().getSerializable(KEY_SERVER_INFO); + serverInfo = ((AddAccountActivity)getActivity()).serverInfo; editAccountName = (EditText)v.findViewById(R.id.account_name); editAccountName.addTextChangedListener(this); @@ -87,81 +85,59 @@ public class AccountDetailsFragment extends Fragment implements TextWatcher { // 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() && calendar.isSupportingEvents()) - try { - LocalCalendar.create(account, getActivity().getContentResolver(), calendar); - syncCalendars = true; - } catch (LocalStorageException e) { - Toast.makeText(getActivity(), "Couldn't create calendar: " + 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); + addSync(account, ContactsContract.AUTHORITY, serverInfo.getAddressBooks(), null); - // ... and notes - boolean syncNotes = false; - for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars()) - if (calendar.isEnabled() && calendar.isSupportingNotes()) - try { - LocalNotebook.create(account, getActivity().getContentResolver(), calendar); - syncNotes = true; - } catch (LocalStorageException e) { - Toast.makeText(getActivity(), "Couldn't create notebook: " + e.getMessage(), Toast.LENGTH_LONG).show(); - } - if (syncNotes) { - ContentResolver.setIsSyncable(account, NoteContract.AUTHORITY, 1); - ContentResolver.setSyncAutomatically(account, NoteContract.AUTHORITY, true); - } else - ContentResolver.setIsSyncable(account, NoteContract.AUTHORITY, 0); + addSync(account, CalendarContract.AUTHORITY, serverInfo.getCalendars(), new AddSyncCallback() { + @Override + public void createLocalCollection(Account account, ServerInfo.ResourceInfo calendar) throws LocalStorageException { + LocalCalendar.create(account, getActivity().getContentResolver(), calendar); + } + }); - // ... and tasks - boolean syncTasks = false; - for (ServerInfo.ResourceInfo calendar : serverInfo.getCalendars()) - if (calendar.isEnabled() && calendar.isSupportingTasks()) - try { - LocalTaskList.create(account, getActivity().getContentResolver(), calendar); - syncTasks = true; - } catch (LocalStorageException e) { - Toast.makeText(getActivity(), "Couldn't create task list: " + e.getMessage(), Toast.LENGTH_LONG).show(); - } - if (syncTasks) { - ContentResolver.setIsSyncable(account, TaskContract.AUTHORITY, 1); - ContentResolver.setSyncAutomatically(account, TaskContract.AUTHORITY, true); - } else - ContentResolver.setIsSyncable(account, TaskContract.AUTHORITY, 0); + addSync(account, LocalTaskList.TASKS_AUTHORITY, serverInfo.getTodoLists(), new AddSyncCallback() { + @Override + public void createLocalCollection(Account account, ServerInfo.ResourceInfo todoList) throws LocalStorageException { + LocalTaskList.create(account, getActivity().getContentResolver(), todoList); + } + }); getActivity().finish(); } else Toast.makeText(getActivity(), "Couldn't create account (account with this name already existing?)", Toast.LENGTH_LONG).show(); } - + protected interface AddSyncCallback { + void createLocalCollection(Account account, ServerInfo.ResourceInfo resource) throws LocalStorageException; + } + + protected void addSync(Account account, String authority, List resourceList, AddSyncCallback callback) { + boolean sync = false; + for (ServerInfo.ResourceInfo resource : resourceList) + if (resource.isEnabled()) { + sync = true; + if (callback != null) + try { + callback.createLocalCollection(account, resource); + } catch(LocalStorageException e) { + Log.e(TAG, "Couldn't add sync collection", e); + Toast.makeText(getActivity(), "Couldn't set up synchronization for " + authority, Toast.LENGTH_LONG).show(); + } + } + if (sync) { + ContentResolver.setIsSyncable(account, authority, 1); + ContentResolver.setSyncAutomatically(account, authority, true); + } else + ContentResolver.setIsSyncable(account, authority, 0); + } + + // input validation @Override diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/AddAccountActivity.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/AddAccountActivity.java index e205554c..eb32e74b 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/AddAccountActivity.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/AddAccountActivity.java @@ -14,12 +14,17 @@ import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.R; +import at.bitfire.davdroid.resource.ServerInfo; public class AddAccountActivity extends Activity { + protected ServerInfo serverInfo; + + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -40,6 +45,11 @@ public class AddAccountActivity extends Activity { return true; } + public void installTasksApp(View view) { + final Intent intent = new Intent(Intent.ACTION_VIEW).setData(Uri.parse("market://details?id=org.dmfs.tasks")); + startActivity(intent); + } + public void showHelp(MenuItem item) { startActivityForResult(new Intent(Intent.ACTION_VIEW, Uri.parse(Constants.WEB_URL_HELP)), 0); } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/InstallAppsFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/InstallAppsFragment.java new file mode 100644 index 00000000..334acfce --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/InstallAppsFragment.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package at.bitfire.davdroid.ui.setup; + +import android.app.DialogFragment; +import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +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.TextView; + +import at.bitfire.davdroid.R; +import at.bitfire.davdroid.resource.LocalTaskList; + +public class InstallAppsFragment extends Fragment implements Runnable { + private static final String TAG = "davdroid.setup"; + + final protected Handler timerHandler = new Handler(); + + + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.setup_install_apps, container, false); + setHasOptionsMenu(true); + + return v; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.only_skip, menu); + } + + @Override + public void onResume() { + super.onResume(); + timerHandler.postDelayed(this, 1000); + } + + @Override + public void onPause() { + super.onPause(); + timerHandler.removeCallbacks(this); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.skip: + skip(true); + break; + default: + return false; + } + return true; + } + + @Override + public void run() { + if (LocalTaskList.isAvailable(getActivity())) + skip(false); + else + timerHandler.postDelayed(this, 1000); + } + + protected void skip(boolean addToBackStack) { + FragmentManager fm = getFragmentManager(); + + if (!addToBackStack) + fm.popBackStack(); + + getFragmentManager().beginTransaction() + .replace(R.id.right_pane, new SelectCollectionsFragment()) + .addToBackStack(null) + .commit(); + } + +} diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/QueryServerDialogFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/QueryServerDialogFragment.java index ed34b07b..96480d38 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/QueryServerDialogFragment.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/QueryServerDialogFragment.java @@ -8,6 +8,7 @@ package at.bitfire.davdroid.ui.setup; import android.app.DialogFragment; +import android.app.Fragment; import android.app.LoaderManager.LoaderCallbacks; import android.content.AsyncTaskLoader; import android.content.Context; @@ -30,12 +31,13 @@ import java.security.cert.CertPathValidatorException; import at.bitfire.davdroid.R; import at.bitfire.davdroid.resource.DavResourceFinder; +import at.bitfire.davdroid.resource.LocalTaskList; import at.bitfire.davdroid.resource.ServerInfo; import at.bitfire.davdroid.webdav.DavException; import lombok.Cleanup; public class QueryServerDialogFragment extends DialogFragment implements LoaderCallbacks { - private static final String TAG = "davdroid.QueryServerDialogFragment"; + private static final String TAG = "davdroid.QueryServer"; public static final String EXTRA_BASE_URI = "base_uri", EXTRA_USER_NAME = "user_name", @@ -71,13 +73,16 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC 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); - + ((AddAccountActivity)getActivity()).serverInfo = serverInfo; + + Fragment nextFragment; + if (!serverInfo.getTodoLists().isEmpty() && !LocalTaskList.isAvailable(getActivity())) + nextFragment = new InstallAppsFragment(); + else + nextFragment = new SelectCollectionsFragment(); + getFragmentManager().beginTransaction() - .replace(R.id.right_pane, selectCollections) + .replace(R.id.right_pane, nextFragment) .addToBackStack(null) .commitAllowingStateLoss(); } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsAdapter.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsAdapter.java index eb0d0fce..cc5bda91 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsAdapter.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsAdapter.java @@ -18,28 +18,50 @@ import android.widget.CheckedTextView; import android.widget.ListAdapter; import at.bitfire.davdroid.R; +import at.bitfire.davdroid.resource.LocalTaskList; import at.bitfire.davdroid.resource.ServerInfo; import lombok.Getter; +/** + * Order of display: + * + * number of rows type + * nAddressBookHeadings (0 or 1) heading: "address books" + * nAddressBooks address book info + * nCalendarHeadings (0 or 1) heading: "calendars" + * nCalendars calendar info + * nNotebookHeadings (0 or 1) heading: "notebooks" + * nNotebooks notebook info + * nTaskListHeadings (0 or 1) heading: "task lists" + * nTaskLists task list info + */ 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; + final static int + TYPE_ADDRESS_BOOKS_HEADING = 0, + TYPE_ADDRESS_BOOKS_ROW = 1, + TYPE_CALENDARS_HEADING = 2, + TYPE_CALENDARS_ROW = 3, + TYPE_TASK_LISTS_HEADING = 4, + TYPE_TASK_LISTS_ROW = 5; protected Context context; protected ServerInfo serverInfo; - @Getter protected int nAddressBooks, nAddressbookHeadings, nCalendars, nCalendarHeadings; + @Getter protected int + nAddressBooks, nAddressBookHeadings, + nCalendars, nCalendarHeadings, + nTaskLists, nTaskListHeadings; public SelectCollectionsAdapter(Context context, ServerInfo serverInfo) { this.context = context; this.serverInfo = serverInfo; - nAddressBooks = (serverInfo.getAddressBooks() == null) ? 0 : serverInfo.getAddressBooks().size(); - nAddressbookHeadings = (nAddressBooks == 0) ? 0 : 1; - nCalendars = (serverInfo.getCalendars() == null) ? 0 : serverInfo.getCalendars().size(); - nCalendarHeadings = (nCalendars == 0) ? 0 : 1; + nAddressBooks = serverInfo.getAddressBooks() == null ? 0 : serverInfo.getAddressBooks().size(); + nAddressBookHeadings = nAddressBooks == 0 ? 0 : 1; + nCalendars = serverInfo.getCalendars() == null ? 0 : serverInfo.getCalendars().size(); + nCalendarHeadings = nCalendars == 0 ? 0 : 1; + nTaskLists = serverInfo.getTodoLists() == null ? 0 : serverInfo.getTodoLists().size(); + nTaskListHeadings = nTaskLists == 0 ? 0 : 1; } @@ -47,17 +69,23 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter @Override public int getCount() { - return nAddressbookHeadings + nAddressBooks + nCalendarHeadings + nCalendars; + return nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars + nTaskListHeadings + nTaskLists; } @Override public Object getItem(int position) { - if (position >= nAddressbookHeadings && - position < (nAddressbookHeadings + nAddressBooks)) - return serverInfo.getAddressBooks().get(position - nAddressbookHeadings); - else if (position >= (nAddressbookHeadings + nAddressBooks + nCalendarHeadings) && - (position < (nAddressbookHeadings + nAddressBooks + nCalendarHeadings + nCalendars))) - return serverInfo.getCalendars().get(position - (nAddressbookHeadings + nAddressBooks + nCalendarHeadings)); + if (position >= nAddressBookHeadings && + position < (nAddressBookHeadings + nAddressBooks)) + return serverInfo.getAddressBooks().get(position - nAddressBookHeadings); + + else if (position >= (nAddressBookHeadings + nAddressBooks + nCalendarHeadings) && + (position < (nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars))) + return serverInfo.getCalendars().get(position - (nAddressBookHeadings + nAddressBooks + nCalendarHeadings)); + + else if (position >= (nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars + nTaskListHeadings) && + (position < (nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars + + nTaskListHeadings + nTaskLists))) + return serverInfo.getTodoLists().get(position - (nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars + nTaskListHeadings)); + return null; } @@ -76,19 +104,26 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter @Override public int getViewTypeCount() { - return 4; + return TYPE_TASK_LISTS_ROW + 1; } @Override public int getItemViewType(int position) { - if ((nAddressbookHeadings != 0) && (position == 0)) + if ((nAddressBookHeadings != 0) && (position == 0)) return TYPE_ADDRESS_BOOKS_HEADING; - else if ((nAddressbookHeadings != 0) && (position > 0) && (position < nAddressbookHeadings + nAddressBooks)) + else if ((nAddressBooks != 0) && (position > 0) && (position < nAddressBookHeadings + nAddressBooks)) return TYPE_ADDRESS_BOOKS_ROW; - else if ((nCalendars != 0) && (position == nAddressbookHeadings + nAddressBooks)) + + else if ((nCalendarHeadings != 0) && (position == nAddressBookHeadings + nAddressBooks)) return TYPE_CALENDARS_HEADING; - else if ((nCalendars != 0) && (position > nAddressbookHeadings + nAddressBooks) && (position < nAddressbookHeadings + nAddressBooks + nCalendarHeadings + nCalendars)) + else if ((nCalendars != 0) && (position > nAddressBookHeadings + nAddressBooks) && (position < nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars)) return TYPE_CALENDARS_ROW; + + else if ((nTaskListHeadings != 0) && (position == nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars)) + return TYPE_TASK_LISTS_HEADING; + else if ((nTaskLists != 0) && (position > nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars) && (position < nAddressBookHeadings + nAddressBooks + nCalendarHeadings + nCalendars + nTaskListHeadings + nTaskLists)) + return TYPE_TASK_LISTS_ROW; + else return IGNORE_ITEM_VIEW_TYPE; } @@ -97,11 +132,13 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter @SuppressLint("InflateParams") public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; + + int viewType = getItemViewType(position); // step 1: get view (either by creating or recycling) if (v == null) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - switch (getItemViewType(position)) { + switch (viewType) { case TYPE_ADDRESS_BOOKS_HEADING: v = inflater.inflate(R.layout.setup_address_books_heading, parent, false); break; @@ -112,21 +149,38 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter case TYPE_CALENDARS_HEADING: v = inflater.inflate(R.layout.setup_calendars_heading, parent, false); break; + case TYPE_TASK_LISTS_HEADING: + v = inflater.inflate(R.layout.setup_task_lists_heading, parent, false); + break; case TYPE_CALENDARS_ROW: + case TYPE_TASK_LISTS_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)) { + switch (viewType) { 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)); + case TYPE_TASK_LISTS_ROW: + setContent((CheckedTextView)v, R.drawable.calendar, (ServerInfo.ResourceInfo) getItem(position)); } - + + // disable task list selection if there's no local task provider + if (viewType == TYPE_TASK_LISTS_ROW && !LocalTaskList.isAvailable(context)) { + final CheckedTextView check = (CheckedTextView)v; + check.setEnabled(false); + check.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + check.setChecked(false); + } + }); + } + return v; } @@ -156,6 +210,6 @@ public class SelectCollectionsAdapter extends BaseAdapter implements ListAdapter @Override public boolean isEnabled(int position) { int type = getItemViewType(position); - return (type == TYPE_ADDRESS_BOOKS_ROW || type == TYPE_CALENDARS_ROW); + return (type == TYPE_ADDRESS_BOOKS_ROW || type == TYPE_CALENDARS_ROW || type == TYPE_TASK_LISTS_ROW); } } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsFragment.java index eaddb345..d85fd127 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsFragment.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/SelectCollectionsFragment.java @@ -24,13 +24,17 @@ 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"; - - + + protected ServerInfo serverInfo; + + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + serverInfo = ((AddAccountActivity)getActivity()).serverInfo; + View v = super.onCreateView(inflater, container, savedInstanceState); setHasOptionsMenu(true); + return v; } @@ -50,7 +54,6 @@ public class SelectCollectionsFragment extends ListFragment { View header = getActivity().getLayoutInflater().inflate(R.layout.setup_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); @@ -80,13 +83,13 @@ public class SelectCollectionsFragment extends ListFragment { 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); + for (ServerInfo.ResourceInfo todoList : serverInfo.getTodoLists()) + todoList.setEnabled(false); ListAdapter adapter = getListView().getAdapter(); for (long id : getListView().getCheckedItemIds()) { @@ -95,14 +98,8 @@ public class SelectCollectionsFragment extends ListFragment { 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.right_pane, accountDetails) + .replace(R.id.right_pane, new AccountDetailsFragment()) .addToBackStack(null) .commitAllowingStateLoss(); break; diff --git a/app/src/main/java/at/bitfire/notebooks/provider/NoteContract.java b/app/src/main/java/at/bitfire/notebooks/provider/NoteContract.java deleted file mode 120000 index 896ac4dc..00000000 --- a/app/src/main/java/at/bitfire/notebooks/provider/NoteContract.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../../notebooks/app/src/main/java/at/bitfire/notebooks/provider/NoteContract.java \ No newline at end of file diff --git a/app/src/main/java/org/dmfs/provider/tasks/TaskContract.java b/app/src/main/java/org/dmfs/provider/tasks/TaskContract.java deleted file mode 100644 index cd116557..00000000 --- a/app/src/main/java/org/dmfs/provider/tasks/TaskContract.java +++ /dev/null @@ -1,1282 +0,0 @@ -/* - * Copyright (C) 2012 Marten Gajda - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package org.dmfs.provider.tasks; - -import android.content.ContentResolver; -import android.net.Uri; - -/** - * Task contract. This class defines the interface to the task provider. - *

- * TODO: Add missing javadoc. - *

- *

- * TODO: Specify extended properties - *

- *

- * TODO: Add CONTENT_URI for the attachment store. - *

- *

- * TODO: Also, we could use some refactoring... - *

- * - * @author Marten Gajda - * @author Tobias Reinsch - */ -public final class TaskContract { - - /** - * Task provider authority. - */ - // TODO how to do this better? - public static final String AUTHORITY = "de.azapps.mirakel.provider"; - public static final String AUTHORITY_DMFS = "org.dmfs.tasks"; - - /** - * Base content URI. - */ - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); - - /** - * URI parameter to signal that the caller is a sync adapter. - */ - public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter"; - - /** - * URI parameter to signal the request of the extended properties of a task. - */ - public static final String LOAD_PROPERTIES = "load_properties"; - - /** - * URI parameter to submit the account name of the account we operate on. - */ - public static final String ACCOUNT_NAME = "account_name"; - - /** - * URI parameter to submit the account type of the account we operate on. - */ - public static final String ACCOUNT_TYPE = "account_type"; - - /** - * Account type for local, unsynced task lists. - */ - public static final String LOCAL_ACCOUNT = "LOCAL"; - - - /** - * Private constructor to prevent instantiation. - */ - private TaskContract() { - } - - /** - * A set of columns for synchronization purposes. These columns exist in {@link Tasks} and in {@link TaskLists} but have different meanings. Only sync - * adapters are allowed to change these values. - * - * @author Marten Gajda - */ - public interface CommonSyncColumns { - - /** - * A unique Sync ID as set by the sync adapter. - *

- * Value: String - *

- */ - public static final String _SYNC_ID = "_sync_id"; - - /** - * Sync version as set by the sync adapter. - *

- * Value: String - *

- */ - public static final String SYNC_VERSION = "sync_version"; - - /** - * Indicates that a task or a task list has been changed. - *

- * Value: Integer - *

- */ - public static final String _DIRTY = "_dirty"; - - /** - * A general purpose column for the sync adapter. - *

- * Value: String - *

- */ - public static final String SYNC1 = "sync1"; - - /** - * A general purpose column for the sync adapter. - *

- * Value: String - *

- */ - public static final String SYNC2 = "sync2"; - - /** - * A general purpose column for the sync adapter. - *

- * Value: String - *

- */ - public static final String SYNC3 = "sync3"; - - /** - * A general purpose column for the sync adapter. - *

- * Value: String - *

- */ - public static final String SYNC4 = "sync4"; - - /** - * A general purpose column for the sync adapter. - *

- * Value: String - *

- */ - public static final String SYNC5 = "sync5"; - - /** - * A general purpose column for the sync adapter. - *

- * Value: String - *

- */ - public static final String SYNC6 = "sync6"; - - /** - * A general purpose column for the sync adapter. - *

- * Value: String - *

- */ - public static final String SYNC7 = "sync7"; - - /** - * A general purpose column for the sync adapter. - *

- * Value: String - *

- */ - public static final String SYNC8 = "sync8"; - - } - - /** - * Additional sync columns for task lists. - * - * @author Marten Gajda - */ - public interface TaskListSyncColumns { - - /** - * The name of the account this list belongs to. This field is write-once. - *

- * Value: String - *

- */ - public static final String ACCOUNT_NAME = "account_name"; - - /** - * The type of the account this list belongs to. This field is write-once. - *

- * Value: String - *

- */ - public static final String ACCOUNT_TYPE = "account_type"; - } - - /** - * Additional sync columns for tasks. - * - * @author Marten Gajda - */ - public interface TaskSyncColumns { - /** - * The UID of a task. This is field can be changed by a sync adapter only. - *

- * Value: String - *

- */ - public static final String _UID = "_uid"; - - /** - * Deleted flag of a task. This is set to 1 by the content provider when a task app deletes a task. The sync adapter has to remove the task - * again to finish the removal. This value is read-only. - *

- * Value: Integer - *

- *

- * read-only - *

- */ - public static final String _DELETED = "_deleted"; - } - - /** - * Data columns of task lists. - * - * @author Marten Gajda - */ - public interface TaskListColumns { - - /** - * List ID. - *

- * Value: Long - *

- *

- * read-only - *

- */ - public static final String _ID = "_id"; - - /** - * The name of the task list. - *

- * Value: String - *

- */ - public static final String LIST_NAME = "list_name"; - - /** - * The color of this list as integer (0xaarrggbb). Only the sync adapter can change this. - *

- * Value: Integer - *

- */ - public static final String LIST_COLOR = "list_color"; - - /** - * The access level a user has on this list. This value is not used yet, sync adapters should set it to 0. - *

- * Value: Integer - *

- */ - public static final String ACCESS_LEVEL = "list_access_level"; - - /** - * Indicates that a task list is set to be visible. - *

- * Value: Integer (0 or 1) - *

- */ - public static final String VISIBLE = "visible"; - - /** - * Indicates that a task list is set to be synced. - *

- * Value: Integer (0 or 1) - *

- */ - public static final String SYNC_ENABLED = "sync_enabled"; - - /** - * The email address of the list owner. - *

- * Value: String - *

- */ - public static final String OWNER = "list_owner"; - - } - - /** - * The task list table holds one entry for each task list. - * - * @author Marten Gajda - */ - public static final class TaskLists implements TaskListColumns, TaskListSyncColumns, - CommonSyncColumns { - public static final String CONTENT_URI_PATH = "tasklists"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + CONTENT_URI_PATH); - - public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + - AUTHORITY + "." + CONTENT_URI_PATH; - - public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + AUTHORITY + - "." + CONTENT_URI_PATH; - - /** - * The default sort order. - */ - public static final String DEFAULT_SORT_ORDER = ACCOUNT_NAME + ", " + LIST_NAME; - - /** - * An array of columns only a sync adapter is allowed to change. - */ - public static final String[] SYNC_ADAPTER_COLUMNS = new String[] { ACCESS_LEVEL, _DIRTY, OWNER, SYNC1, SYNC2, SYNC3, SYNC4, SYNC5, SYNC6, SYNC7, SYNC8, - _SYNC_ID, SYNC_VERSION, - }; - - } - - /** - * Task data columns. Defines all the values a task can have at most once. - * - * @author Marten Gajda - */ - public interface TaskColumns { - - /** - * The row id of a task. This value is read-only - *

- * Value: Integer - *

- */ - public static final String _ID = "_id"; - - /** - * The id of the list this task belongs to. This value is write-once and must not be null. - *

- * Value: Integer - *

- */ - public static final String LIST_ID = "list_id"; - - /** - * The title of the task. - *

- * Value: String - *

- */ - public static final String TITLE = "title"; - - /** - * The location of the task. - *

- * Value: String - *

- */ - public static final String LOCATION = "location"; - - /** - * A geographic location related to the task. The should be a string in the format "longitude,latitude". - *

- * Value: String - *

- */ - public static final String GEO = "geo"; - - /** - * The description of a task. - *

- * Value: String - *

- */ - public static final String DESCRIPTION = "description"; - - /** - * An URL for this task. Must be a valid URL if not null- - *

- * Value: String - *

- */ - public static final String URL = "url"; - - /** - * The email address of the organizer if any, {@code null} otherwise. - *

- * Value: String - *

- */ - public static final String ORGANIZER = "organizer"; - - /** - * The priority of a task. This is an Integer between zero and 9. Zero means there is no priority set. 1 is the highest priority and 9 the lowest. - *

- * Value: Integer - *

- */ - public static final String PRIORITY = "priority"; - - /** - * The default value of {@link #PRIORITY}. - */ - public static final int PRIORITY_DEFAULT = 0; - - /** - * The classification of a task. This value must be either null or one of {@link #CLASSIFICATION_PUBLIC}, {@link #CLASSIFICATION_PRIVATE}, - * {@link #CLASSIFICATION_CONFIDENTIAL}. - *

- * Value: Integer - *

- */ - public static final String CLASSIFICATION = "class"; - - /** - * Classification value for public tasks. - */ - public static final int CLASSIFICATION_PUBLIC = 0; - - /** - * Classification value for private tasks. - */ - public static final int CLASSIFICATION_PRIVATE = 1; - - /** - * Classification value for confidential tasks. - */ - public static final int CLASSIFICATION_CONFIDENTIAL = 2; - - /** - * Default value of {@link #CLASSIFICATION}. - */ - public static final Integer CLASSIFICATION_DEFAULT = null; - - /** - * Date of completion of this task in milliseconds since the epoch or {@code null} if this task has not been completed yet. - *

- * Value: Long - *

- */ - public static final String COMPLETED = "completed"; - - /** - * Indicates that the date of completion is an all-day date. - *

- * Value: Integer - *

- */ - public static final String COMPLETED_IS_ALLDAY = "completed_is_allday"; - - /** - * A number between 0 and 100 that indicates the progress of the task or null. - *

- * Value: Integer (0-100) - *

- */ - public static final String PERCENT_COMPLETE = "percent_complete"; - - /** - * The status of this task. One of {@link #STATUS_NEEDS_ACTION},{@link #STATUS_IN_PROCESS}, {@link #STATUS_COMPLETED}, {@link #STATUS_CANCELLED}. - *

- * Value: Integer - *

- */ - public static final String STATUS = "status"; - - /** - * A specific status indicating that nothing has been done yet. - */ - public static final int STATUS_NEEDS_ACTION = 0; - - /** - * A specific status indicating that some work has been done. - */ - public static final int STATUS_IN_PROCESS = 1; - - /** - * A specific status indicating that the task is completed. - */ - public static final int STATUS_COMPLETED = 2; - - /** - * A specific status indicating that the task has been cancelled. - */ - public static final int STATUS_CANCELLED = 3; - - /** - * The default status is "needs action". - */ - public static final int STATUS_DEFAULT = STATUS_NEEDS_ACTION; - - /** - * A flag that indicates a task is new (i.e. not work has been done yet). This flag is read-only. Its value is 1 when - * {@link #STATUS} equals {@link #STATUS_NEEDS_ACTION} and 0 otherwise. - *

- * Value: Integer - *

- *

- * read-only - *

- */ - public static final String IS_NEW = "is_new"; - - /** - * A flag that indicates a task is closed (no more work has to be done). This flag is read-only. Its value is 1 when - * {@link #STATUS} equals {@link #STATUS_COMPLETED} or {@link #STATUS_CANCELLED} and 0 otherwise. - *

- * Value: Integer - *

- *

- * read-only - *

- */ - public static final String IS_CLOSED = "is_closed"; - - /** - * An individual color for this task in the format 0xaarrggbb or {@code null} to use {@link TaskListColumns#LIST_COLOR} instead. - *

- * Value: Integer - *

- */ - public static final String TASK_COLOR = "task_color"; - - /** - * When this task starts in milliseconds since the epoch. - *

- * Value: Long - *

- */ - public static final String DTSTART = "dtstart"; - - /** - * Boolean: flag that indicates that this is an all-day task. - */ - public static final String IS_ALLDAY = "is_allday"; - - /** - * When this task has been created in milliseconds since the epoch. - *

- * Value: Long - *

- */ - public static final String CREATED = "created"; - - /** - * When this task had been modified the last time in milliseconds since the epoch. - *

- * Value: Long - *

- */ - public static final String LAST_MODIFIED = "last_modified"; - - /** - * String: An Olson Id of the time zone of this task. If this value is null, it's automatically replaced by the local time zone. - */ - public static final String TZ = "tz"; - - /** - * When this task is due in milliseconds since the epoch. Only one of {@link #DUE} or {@link #DURATION} must be supplied (or none of both if the task - * has no due date). - *

- * Value: Long - *

- */ - public static final String DUE = "due"; - - /** - * The duration of this task. Only one of {@link #DUE} or {@link #DURATION} must be supplied (or none of both if the task has no due date). Setting a - * {@link #DURATION} is not allowed when {@link #DTSTART} is null. The Value must be a duration string as in RFC 5545 Section 3.3.6. - *

- * Value: String - *

- */ - public static final String DURATION = "duration"; - - /** - * A comma separated list of time Strings in RFC 5545 format (see RFC 5545 Section 3.3.4 - * and RFC 5545 Section 3.3.5) that contains dates of instances of e recurring task. - * All-day tasks must use the DATE format specified in section 3.3.4 of RFC 5545. - * - * This value must be {@code null} for exception instances. - *

- * Value: String - *

- */ - public static final String RDATE = "rdate"; - - /** - * A comma separated list of time Strings in RFC 5545 format (see RFC 5545 Section 3.3.4 - * and RFC 5545 Section 3.3.5) that contains dates of exceptions of a recurring task. - * All-day tasks must use the DATE format specified in section 3.3.4 of RFC 5545. - * - * This value must be {@code null} for exception instances. - *

- * Value: String - *

- */ - public static final String EXDATE = "exdate"; - - /** - * A recurrence rule as specified in RFC 5545 Section 3.3.10. - * - * This value must be {@code null} for exception instances. - *

- * Value: String - *

- */ - public static final String RRULE = "rrule"; - - /** - * The _sync_id of the original event if this is an exception, null otherwise. Only one of {@link #ORIGINAL_INSTANCE_SYNC_ID} or - * {@link #ORIGINAL_INSTANCE_ID} must be set if this task is an exception. The other one will be updated by the content provider. - *

- * Value: String - *

- */ - public static final String ORIGINAL_INSTANCE_SYNC_ID = "original_instance_sync_id"; - - /** - * The row id of the original event if this is an exception, null otherwise. Only one of {@link #ORIGINAL_INSTANCE_SYNC_ID} or - * {@link #ORIGINAL_INSTANCE_ID} must be set if this task is an exception. The other one will be updated by the content provider. - *

- * Value: Long - *

- */ - public static final String ORIGINAL_INSTANCE_ID = "original_instance_id"; - - /** - * The time in milliseconds since the Epoch of the original instance that is overridden by this instance or null if this task is not an - * exception. - *

- * Value: Long - *

- */ - public static final String ORIGINAL_INSTANCE_TIME = "original_instance_time"; - - /** - * A flag indicating that the original instance was an all-day task. - *

- * Value: Integer - *

- */ - public static final String ORIGINAL_INSTANCE_ALLDAY = "original_instance_allday"; - - /** - * The row id of the parent task. null if the task has no parent task. - *

- * Value: Long - *

- */ - public static final String PARENT_ID = "parent_id"; - - /** - * The sorting of this task under it's parent task. - *

- * Value: String - *

- */ - public static final String SORTING = "sorting"; - - /** - * Indicates how many alarms a task has. 0 means the task has no alarms. - *

- * Value: Integer - *

- * - */ - public static final String HAS_ALARMS = "has_alarms"; - } - - /** - * The task table stores the data of all tasks. - * - * @author Marten Gajda - */ - public static final class Tasks implements TaskColumns, CommonSyncColumns, TaskSyncColumns { - /** - * The name of the account the task belongs to. This is auto-derived from the list the task belongs to. Do not write this value here. - *

- * Value: String - *

- *

- * read-only - *

- */ - public static final String ACCOUNT_NAME = TaskLists.ACCOUNT_NAME; - - /** - * The type of the account the task belongs to. This is auto-derived from the list the task belongs to. Do not write this value here. - *

- * Value: String - *

- *

- * read-only - *

- */ - public static final String ACCOUNT_TYPE = TaskLists.ACCOUNT_TYPE; - - /** - * The name of the list this task belongs to as integer (0xaarrggbb). This is auto-derived from the list the task belongs to. Do not write this value - * here. - *

- * Value: String - *

- *

- * read-only - *

- */ - public static final String LIST_NAME = TaskLists.LIST_NAME; - /** - * The color of the list this task belongs to as integer (0xaarrggbb). This is auto-derived from the list the task belongs to. Do not write this value - * here. To change the color of an individual task use {@code TASK_COLOR} instead. - *

- * Value: Integer - *

- *

- * read-only - *

- */ - public static final String LIST_COLOR = TaskLists.LIST_COLOR; - - /** - * The owner of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. - *

- * Value: String - *

- *

- * read-only - *

- */ - public static final String LIST_OWNER = TaskLists.OWNER; - - /** - * The access level of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. - *

- * Value: Integer - *

- *

- * read-only - *

- */ - public static final String LIST_ACCESS_LEVEL = TaskLists.ACCESS_LEVEL; - - /** - * The visibility of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. - *

- * Value: Integer - *

- *

- * read-only - *

- */ - public static final String VISIBLE = "visible"; - - public static final String CONTENT_URI_PATH = "tasks"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + CONTENT_URI_PATH); - - public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + - AUTHORITY + "." + CONTENT_URI_PATH; - - public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + AUTHORITY + - "." + CONTENT_URI_PATH; - - public static final String DEFAULT_SORT_ORDER = DUE; - - public static final String[] SYNC_ADAPTER_COLUMNS = new String[] { _DIRTY, SYNC1, SYNC2, SYNC3, SYNC4, SYNC5, SYNC6, SYNC7, SYNC8, _SYNC_ID, - SYNC_VERSION, - }; - } - - /** - * Columns of a task instance. - * - * @author Yannic Ahrens - * @author Marten Gajda - */ - public interface InstanceColumns { - /** - * _ID of task this instance belongs to. - *

- * Value: Long - *

- */ - public static final String TASK_ID = "task_id"; - - /** - * The start date of an instance in milliseconds since the epoch or null if the instance has no start date. At present this is read only. - *

- * Value: Long - *

- */ - public static final String INSTANCE_START = "instance_start"; - - /** - * The due date of an instance in milliseconds since the epoch or null if the instance has no due date. At present this is read only. - *

- * Value: Long - *

- */ - public static final String INSTANCE_DUE = "instance_due"; - - /** - * This column should be used in an order clause to sort instances by due date. It contains a slightly modified start date that takes allday tasks into - * account. - *

- * Value: Long - *

- *

- * read-only - *

- */ - public static final String INSTANCE_START_SORTING = "instance_start_sorting"; - - /** - * This column should be used in an order clause to sort instances by due date. It contains a slightly modified due date that takes allday tasks into - * account. - *

- * Value: Long - *

- *

- * read-only - *

- */ - public static final String INSTANCE_DUE_SORTING = "instance_due_sorting"; - - /** - * The duration of an instance in milliseconds or null if the instance has only one of start or due date or none of both. At present this - * is read only. - *

- * Value: Long - *

- */ - public static final String INSTANCE_DURATION = "instance_duration"; - - } - - /** - * Instances of a task. At present this table is read only. Currently it contains exactly one entry per task (and task exception), so it's merely a copy of - * {@link Tasks}. - *

- * TODO: Insert all instances of recurring the tasks. - *

- *

- * TODO: In later releases it's planned to provide a convenient interface to add, change or delete task instances via this URI. - *

- * - * @author Yannic Ahrens - */ - public static final class Instances implements TaskColumns, InstanceColumns { - - /** - * The name of the account the task belongs to. This is auto-derived from the list the task belongs to. Do not write this value here. - *

- * Value: String - *

- *

- * read-only - *

- */ - public static final String ACCOUNT_NAME = TaskLists.ACCOUNT_NAME; - - /** - * The type of the account the task belongs to. This is auto-derived from the list the task belongs to. Do not write this value here. - *

- * Value: String - *

- *

- * read-only - *

- */ - public static final String ACCOUNT_TYPE = TaskLists.ACCOUNT_TYPE; - - /** - * The name of the list this task belongs to as integer (0xaarrggbb). This is auto-derived from the list the task belongs to. Do not write this value - * here. - *

- * Value: String - *

- *

- * read-only - *

- */ - public static final String LIST_NAME = TaskLists.LIST_NAME; - /** - * The color of the list this task belongs to as integer (0xaarrggbb). This is auto-derived from the list the task belongs to. Do not write this value - * here. To change the color of an individual task use {@code TASK_COLOR} instead. - *

- * Value: Integer - *

- *

- * read-only - *

- */ - public static final String LIST_COLOR = TaskLists.LIST_COLOR; - - /** - * The owner of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. - *

- * Value: String - *

- *

- * read-only - *

- */ - public static final String LIST_OWNER = TaskLists.OWNER; - - /** - * The access level of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. - *

- * Value: Integer - *

- *

- * read-only - *

- */ - public static final String LIST_ACCESS_LEVEL = TaskLists.ACCESS_LEVEL; - - /** - * The visibility of the list this task belongs. This is auto-derived from the list the task belongs to. Do not write this value here. - *

- * Value: Integer - *

- *

- * read-only - *

- */ - public static final String VISIBLE = "visible"; - - public static final String CONTENT_URI_PATH = "instances"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + CONTENT_URI_PATH); - - public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + AUTHORITY + - "." + CONTENT_URI_PATH; - - public static final String DEFAULT_SORT_ORDER = INSTANCE_DUE_SORTING; - - } - - /* - * ================================================================================== - * - * Everything below this line is not used yet and subject to change. Don't use it. - * - * ================================================================================== - */ - - /** - * Available values in Categories. - * - * Categories are per account. It's up to the front-end to ensure consistency of category colors across accounts. - * - * @author Marten Gajda - */ - public interface CategoriesColumns { - - public static final String _ID = "_id"; - - public static final String ACCOUNT_NAME = "account_name"; - - public static final String ACCOUNT_TYPE = "account_type"; - - public static final String NAME = "name"; - - public static final String COLOR = "color"; - } - - public static final class Categories implements CategoriesColumns { - - public static final String CONTENT_URI_PATH = "categories"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + CONTENT_URI_PATH); - - public static final String DEFAULT_SORT_ORDER = NAME; - - } - - public interface AlarmsColumns { - public static final String ALARM_ID = "alarm_id"; - - public static final String LAST_TRIGGER = "last_trigger"; - - public static final String NEXT_TRIGGER = "next_trigger"; - } - - public static final class Alarms implements AlarmsColumns { - - public static final String CONTENT_URI_PATH = "alarms"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + CONTENT_URI_PATH); - - } - - public interface PropertySyncColumns { - public static final String SYNC1 = "prop_sync1"; - - public static final String SYNC2 = "prop_sync2"; - - public static final String SYNC3 = "prop_sync3"; - - public static final String SYNC4 = "prop_sync4"; - - public static final String SYNC5 = "prop_sync5"; - - public static final String SYNC6 = "prop_sync6"; - - public static final String SYNC7 = "prop_sync7"; - - public static final String SYNC8 = "prop_sync8"; - } - - public interface PropertyColumns { - - public static final String PROPERTY_ID = "property_id"; - - public static final String TASK_ID = "task_id"; - - public static final String MIMETYPE = "mimetype"; - - public static final String VERSION = "prop_version"; - - public static final String DATA0 = "data0"; - - public static final String DATA1 = "data1"; - - public static final String DATA2 = "data2"; - - public static final String DATA3 = "data3"; - - public static final String DATA4 = "data4"; - - public static final String DATA5 = "data5"; - - public static final String DATA6 = "data6"; - - public static final String DATA7 = "data7"; - - public static final String DATA8 = "data8"; - - public static final String DATA9 = "data9"; - - public static final String DATA10 = "data10"; - - public static final String DATA11 = "data11"; - - public static final String DATA12 = "data12"; - - public static final String DATA13 = "data13"; - - public static final String DATA14 = "data14"; - - public static final String DATA15 = "data15"; - } - - public static final class Properties implements PropertySyncColumns, PropertyColumns { - - public static final String CONTENT_URI_PATH = "properties"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + CONTENT_URI_PATH); - - public static final String DEFAULT_SORT_ORDER = DATA0; - - } - - public interface Property { - /** - * Attached documents. - *

- * Note: Attachments are write-once. To change an attachment you'll have to remove and re-add it. - *

- * - * @author Marten Gajda - */ - public static interface Attachment extends PropertyColumns { - /** - * The mime-type of this property. - */ - public final static String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + - "/attachment"; - - /** - * ID of the attachment. Use this id to store and retrieve the attachment in the attachments table. - *

- * Value: Long - *

- */ - public final static String ATTACHMENT_ID = DATA1; - - /** - * Content-type of the attachment. - *

- * Value: String - *

- */ - public final static String FORMAT = DATA2; - } - - public static interface Attendee extends PropertyColumns { - /** - * The mime-type of this property. - */ - public final static String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/attendee"; - - /** - * Name of the contact, if known. - *

- * Value: String - *

- */ - public final static String NAME = DATA0; - - /** - * Email address of the contact. - *

- * Value: String - *

- */ - public final static String EMAIL = DATA1; - - public final static String ROLE = DATA2; - - public final static String STATUS = DATA3; - - public final static String RSVP = DATA4; - } - - /** - * Categories are immutable. For creation is either the category id or name necessary - * - */ - public static interface Category extends PropertyColumns { - /** - * The mime-type of this property. - */ - public final static String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/category"; - - /** - * Row id of the category. - *

- * Value: Long - *

- */ - public final static String CATEGORY_ID = DATA0; - - /** - * The name of the category - *

- * Value: String - *

- */ - public final static String CATEGORY_NAME = DATA1; - - /** - * The decimal coded color of the category - *

- * Value: Integer - *

- *

- * read-only - *

- */ - public final static String CATEGORY_COLOR = DATA2; - } - - public static interface Comment extends PropertyColumns { - /** - * The mime-type of this property. - */ - public final static String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/comment"; - - /** - * Comment text. - *

- * Value: String - *

- */ - public final static String COMMENT = DATA0; - - /** - * Language code of the comment as defined in RFC5646 or null. - *

- * Value: String - *

- */ - public final static String LANGUAGE = DATA1; - } - - public static interface Contact extends PropertyColumns { - /** - * The mime-type of this property. - */ - public final static String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/contact"; - - public final static String NAME = DATA0; - - public final static String LANGUAGE = DATA1; - } - - public static interface Relation extends PropertyColumns { - /** - * The mime-type of this property. - */ - public final static String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/relation"; - - public final static String RELATED_ID = DATA1; - - public final static String RELATED_TYPE = DATA2; - } - - public static interface Alarm extends PropertyColumns { - - public static final int ALARM_TYPE_NOTHING = 0; - - public static final int ALARM_TYPE_MESSAGE = 1; - - public static final int ALARM_TYPE_EMAIL = 2; - - public static final int ALARM_TYPE_SMS = 3; - - public static final int ALARM_TYPE_SOUND = 4; - - public static final int ALARM_REFERENCE_DUE_DATE = 1; - - public static final int ALARM_REFERENCE_START_DATE = 2; - - /** - * The mime-type of this property. - */ - public final static String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/alarm"; - - /** - * Number of minutes from the reference date when the alarm goes off. If the value is < 0 the alarm will go off after the reference date. - *

- * Value: Integer - *

- */ - public final static String MINUTES_BEFORE = DATA0; - - /** - * The reference date for the alarm. Either {@link ALARM_REFERENCE_DUE_DATE} or {@link ALARM_REFERENCE_START_DATE}. - *

- * Value: Integer - *

- */ - public final static String REFERENCE = DATA1; - - /** - * A message that appears with the alarm. - *

- * Value: String - *

- */ - public final static String MESSAGE = DATA2; - - /** - * The type of the alarm. Use the provided alarm types {@link ALARM_TYPE_MESSAGE}, {@link ALARM_TYPE_SOUND}, {@link ALARM_TYPE_NOTHING}, - * {@link ALARM_TYPE_EMAIL} and {@link ALARM_TYPE_SMS}. - *

- * Value: Integer - *

- */ - public final static String ALARM_TYPE = DATA3; - } - - } - -} diff --git a/app/src/main/java/org/dmfs/provider/tasks/TaskContract.java b/app/src/main/java/org/dmfs/provider/tasks/TaskContract.java new file mode 120000 index 00000000..51ffe3f3 --- /dev/null +++ b/app/src/main/java/org/dmfs/provider/tasks/TaskContract.java @@ -0,0 +1 @@ +/home/rfc2822/Entwicklung/Android/task-provider/src/org/dmfs/provider/tasks/TaskContract.java \ No newline at end of file diff --git a/app/src/main/java/org/dmfs/provider/tasks/UriFactory.java b/app/src/main/java/org/dmfs/provider/tasks/UriFactory.java new file mode 120000 index 00000000..f77e8c16 --- /dev/null +++ b/app/src/main/java/org/dmfs/provider/tasks/UriFactory.java @@ -0,0 +1 @@ +/home/rfc2822/Entwicklung/Android/task-provider/src/org/dmfs/provider/tasks/UriFactory.java \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/navigation_skip.png b/app/src/main/res/drawable-hdpi/navigation_skip.png new file mode 100644 index 0000000000000000000000000000000000000000..62035e89414695b790fee4a7da33eee17fe2c1f0 GIT binary patch literal 661 zcmV;G0&4wA3Jfp~&iViV0wPI7 zK~!ko?U*5N+&~b8Us{Ej1YFRQ0(u~%mgeo)&$|YSii(PgUbEij+$17%k{dJIyd;~W zy@8Ea0KNh^2G9WbuB!D|)@1@BG6C=nz%*Ri2XIXC*36!B*|B;vfUN*bTGx)kDvx1( zCSYb8lD`3*v`Q0_hh}z{!@9o2X7-C@4Pe$@U6M~`_A`fdO(8RTBw0nz_g}pyS)18o z4(l4DW_C~VFg(|>W+Z>akvW9j^-J8=6@Yc(`a(pGMzFh|Kv>yllDFYSt)kLCfOm0Z z4q-hf099=OEW?MY9hr+<)@1^%$b10sp;bEQh}T}Mn}MjYuE?y+>^_HeGZ01QUfBD@ zns&r%FV@XKXl%75GFN6+=df-DLSuI%X8;ZojXku!5xv=+q|pDM7dIdxXNkuG0G6t{ z&ShN;h{$IEr-{;qs;+Wb7Xu=43gA{E{fGI^wby>p6i2K%I!d9WsP1Dk>@}DjKRkL4trN5DU*y00000NkvXXu0mjfQ$HL1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/navigation_skip.png b/app/src/main/res/drawable-mdpi/navigation_skip.png new file mode 100644 index 0000000000000000000000000000000000000000..0fc68909e1c2ac06aef548f86592fca20ca24f70 GIT binary patch literal 488 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&kwgRg#C3xqD8-3I+zoQcoAhkch)?rz!d~I|{VgE6k8$ z3DsZ88tA-f#;YuyZ4o*39NV@<{A1qL=DAA1R5aIU#;mTeD+$w5iaoza?PBrxHA(pU zz9-M_-g*1xP@wzY(pCYh`-!_>seJyxc7Z+Wz=<<$yEoL9&Pu!+!LT5>^0v%v@ATUZ zyZu$}GKgQ?HPNK}d%ukKO~wZGglTuaJ8`U)z41`CBld>JET-)TD);26?wxtFNV@oY z=|l#@BSDv{mtF|v*?8mFo!a0>!VC#%#t&3CMjc3&)xOlYM7Y>9dx7j)Z%^NH`R5TE zf$Bqoj3b+LJ+sT_KLhh4E}0fcuHCkzb@%z=Hw)??sBbNA5IDqKFf~Fy&1_Y&D^rWW zE(fbJ_q1{M9P z{>h-!a(a71$C=I-whhnv*84N;QC?InSh?u|-^A-|`wba4_|#lyGcbDg=M7_Fww>Tb zCI|bxy+wYVD^!(=xzEKiwm$yFc)&GjhMAzLOG@Iwo!Sa~n-v%FHpC@)rcZzNs^}e8 z$Kf>{8EbhiD=%^vjEpXL$ECq@ImqO?_lu2U&(}3HJ-fwlPH2|3`l3ju{;4~Yvkj(3 zyB7O0EUvIkIQmnoVdi#*V+`B6UhA*52yb^d`E}N_b5n|2P67SFHS0IugXFAkt*SL= zz}D6^J_}R&bhxQF+`+N9_mVBcly~e)zC_Ff@@3K%?)aw?$5nrK;qjLXo`z!6HknQ}(GrB))&T6Jb+t^M` zvUV+>Y8lRY#Qoka`|mw7mxCm@@~c#~F5R)u=y@Ukj-V}Dk9?Mm^FHU#7#JPv8oeEA z%<=c^sV6NK?vA>%aMOEjqwj^@^WI&!c4!hX3^Hf%)$M<0l=@7+{Xxw0_}V}tbr^VV be^&qPN=DwkjO3-jgw5dT>gTe~DWM4fN_Ahw literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/navigation_skip.png b/app/src/main/res/drawable-xxhdpi/navigation_skip.png new file mode 100644 index 0000000000000000000000000000000000000000..7608259d286a040a132b83a2bac477498fd9c4db GIT binary patch literal 1140 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoY)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP~t0WJr>5IuxwhRm`jGiuzAr-gY&RG~N?I?20{?@@G z3JwPOYKzo5W}MLx4Y~3~f5pM3Uf1vW4qKGWB66}93mCrP5miW1I&@~S5*s5^@a8i) z#!t$&Z{F_tZtwi3KMX(byK{GX>U##)KoL|lMatmVmunZda@|{>@MAf{KB2(wnfcd` zZq?ex#kY_30ZYUGPT8pJfb!PO>%zstpxszqD5raXu&9@U0#UUqD7q=OG<`%rm zWKm>(O2AfWSNgMdtJp>RIE_CW3*Ke4=#hyjk6e1@rO;N9gJ=3(zOWxS-f?(4qm0nS ztI<=9mU~}`5-_$GxLA`Ysd(k}`spnb&R+d8`$N)F8TGJJb)3^Xj1v|HZDV|Myk=+D zisg|pO6M5*yN`JNTsmR(&-8|H&GmPCP10U2@OCLq+qeF*sv1Ln!Q>XNn=4l=zZsLn z^g-*X$bsc8UORPKa`X%tcN{z`to^jTt7;b5lIW>LQvx*4d+l(U!+3A`_h}v9?gTi! zVR@BlHhl)ep1AwW8-zc9<*R=f z?l4QvZE587-r>UX;9sjmHSave9Xbz%1RHk$ly#T0V=u^CTf6EuFz&wx9Ls$2(4T=# z^ziv-KzZqiQ}$kL*KS4L)e~f_;D2<~F2iukH9uf%S}~s!(t5Ps!S&3`tU88uO3S`{ zp066C#AI+xW_idFhwEVNt2;JEv!yef16iEV^HL@3NO#w+nP-6(g$w@XWVYtNa#jau z@QyBtDu&z7HzobJ2{hruz26&V8*C`NtLK@1pfJf*MzqM(p5glWWbH>PNtu@2-Yy{P zblCZU5y0}`Ue%-^!PV2IZQcpwXg%^=_AtE|m|(LtH;R5#3NYUcRP#ZeSvo2klmxAq zrM6B^T+Ed5$gi!JbHPe$Nwb$cGj_8H700Yt`lMtn@7BC?rTNC3d#^v|Jiwp*cFz=W zp!d6!v_3tbQM>(`pZD$~EDzYe|B`F3{qx&x^1`j(zORaTeIV?m{R`bWy=%5kfBi_P jb>F@?^eidj`G@tHAM=dd|7V{7%LfKeS3j3^P6 + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/setup_install_apps.xml b/app/src/main/res/layout/setup_install_apps.xml new file mode 100644 index 00000000..4189256d --- /dev/null +++ b/app/src/main/res/layout/setup_install_apps.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + +