diff --git a/app/build.gradle b/app/build.gradle
index 0cc1ae6b..42adb17a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -68,6 +68,7 @@ dependencies {
compile 'com.android.support:design:23.+'
compile 'com.android.support:preference-v7:23.+'
+ compile 'com.github.yukuku:ambilwarna:2.0.1'
compile project(':MemorizingTrustManager')
androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.1.2'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0203ef80..aed7d5b3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -131,6 +131,8 @@
+
> 24);
+ int color = colorWithAlpha & 0xFFFFFF;
+ return String.format("#%06X%02X", color, alpha);
+ }
+
}
diff --git a/app/src/main/java/at/bitfire/davdroid/model/DavService.java b/app/src/main/java/at/bitfire/davdroid/model/DavService.java
index 26c94462..1fcf7c40 100644
--- a/app/src/main/java/at/bitfire/davdroid/model/DavService.java
+++ b/app/src/main/java/at/bitfire/davdroid/model/DavService.java
@@ -23,7 +23,7 @@ public class DavService {
service.accountName = values.getAsString(ServiceDB.Services.ACCOUNT_NAME);
service.service = values.getAsString(ServiceDB.Services.SERVICE);
service.principal = values.getAsString(ServiceDB.Services.PRINCIPAL);
- //service.lastRefresh = values.getAsLong(ServiceDB.Services.LAST_REFRESH);
+ //FIXME service.lastRefresh = values.getAsLong(ServiceDB.Services.LAST_REFRESH);
return service;
}
diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java
index 1c7cd06f..839037a6 100644
--- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java
+++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java
@@ -66,6 +66,9 @@ public class CalendarsSyncAdapterService extends Service {
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
Constants.log.info("Starting calendar sync (" + authority + ")");
+ // required for ical4j and dav4android (ServiceLoader)
+ Thread.currentThread().setContextClassLoader(getContext().getClassLoader());
+
try {
updateLocalCalendars(provider, account);
diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java
index 2d2c9505..982465e3 100644
--- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java
+++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java
@@ -62,6 +62,9 @@ public class ContactsSyncAdapterService extends Service {
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
Constants.log.info("Starting address book sync (" + authority + ")");
+ // required for dav4android (ServiceLoader)
+ Thread.currentThread().setContextClassLoader(getContext().getClassLoader());
+
long service = getService(account);
CollectionInfo remote = remoteAddressBook(service);
diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java
index 6c44bfee..1e3106e7 100644
--- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java
+++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.java
@@ -107,9 +107,6 @@ abstract public class SyncManager {
this.authority = authority;
this.syncResult = syncResult;
- // required for ical4j and dav4android (ServiceLoader)
- Thread.currentThread().setContextClassLoader(context.getClassLoader());
-
// get account settings and log to file (if requested)
settings = new AccountSettings(context, account);
try {
@@ -423,7 +420,7 @@ abstract public class SyncManager {
syncResult.stats.numDeletes++;
} else {
// contact is still on server, check whether it has been updated remotely
- GetETag getETag = (GetETag) remote.properties.get(GetETag.NAME);
+ GetETag getETag = (GetETag)remote.properties.get(GetETag.NAME);
if (getETag == null || getETag.eTag == null)
throw new DavException("Server didn't provide ETag");
String localETag = localResources.get(localName).getETag(),
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 2bef01f4..395fbabb 100644
--- a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.java
+++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.java
@@ -65,6 +65,9 @@ public class TasksSyncAdapterService extends Service {
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient providerClient, SyncResult syncResult) {
Constants.log.info("Starting task sync (" + authority + ")");
+ // required for ical4j and dav4android (ServiceLoader)
+ Thread.currentThread().setContextClassLoader(getContext().getClassLoader());
+
try {
@Cleanup TaskProvider provider = TaskProvider.acquire(getContext().getContentResolver(), TaskProvider.ProviderName.OpenTasks);
if (provider == null)
diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java b/app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java
index c576cb31..a700e565 100644
--- a/app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java
+++ b/app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java
@@ -28,6 +28,7 @@ import android.content.ServiceConnection;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
+import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
@@ -56,6 +57,8 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
+import org.apache.commons.lang3.BooleanUtils;
+
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
@@ -93,13 +96,18 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
setContentView(R.layout.activity_account);
+ Drawable icMenu = Build.VERSION.SDK_INT >= 21 ? getDrawable(R.drawable.ic_menu_light) :
+ getResources().getDrawable(R.drawable.ic_menu_light);
+
// CardDAV toolbar
tbCardDAV = (Toolbar)findViewById(R.id.carddav_menu);
+ tbCardDAV.setOverflowIcon(icMenu);
tbCardDAV.inflateMenu(R.menu.carddav_actions);
tbCardDAV.setOnMenuItemClickListener(this);
// CalDAV toolbar
tbCalDAV = (Toolbar)findViewById(R.id.caldav_menu);
+ tbCalDAV.setOverflowIcon(icMenu);
tbCalDAV.inflateMenu(R.menu.caldav_actions);
tbCalDAV.setOnMenuItemClickListener(this);
@@ -165,6 +173,11 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
intent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, accountInfo.caldav.id);
startService(intent);
break;
+ case R.id.create_calendar:
+ intent = new Intent(this, CreateCalendarActivity.class);
+ intent.putExtra(CreateCalendarActivity.EXTRA_ACCOUNT, account);
+ startActivity(intent);
+ break;
}
return false;
}
@@ -231,14 +244,14 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
final ArrayAdapter adapter = (ArrayAdapter)list.getAdapter();
final CollectionInfo info = adapter.getItem(position);
- PopupMenu popup = new PopupMenu(AccountActivity.this, view, Gravity.CENTER);
+ PopupMenu popup = new PopupMenu(AccountActivity.this, view);
popup.inflate(R.menu.account_collection_operations);
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.delete_collection:
- DeleteCollectionFragment.newInstance(account, info).show(getSupportFragmentManager(), null);
+ DeleteCollectionFragment.ConfirmDeleteCollectionFragment.newInstance(account, info).show(getSupportFragmentManager(), null);
break;
}
return true;
@@ -331,7 +344,6 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
davService = (DavService.InfoBinder)service;
davService.addRefreshingStatusListener(this, false);
- SQLiteDatabase db;
forceLoad();
}
@@ -442,10 +454,12 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
CheckBox checked = (CheckBox)v.findViewById(R.id.checked);
checked.setChecked(info.selected);
+ View vColor = v.findViewById(R.id.color);
if (info.color != null) {
- View vColor = v.findViewById(R.id.color);
+ vColor.setVisibility(View.VISIBLE);
vColor.setBackgroundColor(info.color);
- }
+ } else
+ vColor.setVisibility(View.GONE);
TextView tv = (TextView)v.findViewById(R.id.title);
tv.setText(TextUtils.isEmpty(info.displayName) ? info.url : info.displayName);
@@ -462,10 +476,10 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu
tv.setVisibility(info.readOnly ? View.VISIBLE : View.GONE);
tv = (TextView)v.findViewById(R.id.events);
- tv.setVisibility(info.supportsVEVENT ? View.VISIBLE : View.GONE);
+ tv.setVisibility(BooleanUtils.isTrue(info.supportsVEVENT) ? View.VISIBLE : View.GONE);
tv = (TextView)v.findViewById(R.id.tasks);
- tv.setVisibility(info.supportsVTODO ? View.VISIBLE : View.GONE);
+ tv.setVisibility(BooleanUtils.isTrue(info.supportsVTODO) ? View.VISIBLE : View.GONE);
return v;
}
diff --git a/app/src/main/java/at/bitfire/davdroid/ui/CreateAddressBookActivity.java b/app/src/main/java/at/bitfire/davdroid/ui/CreateAddressBookActivity.java
index 9aa4d285..888bc7f7 100644
--- a/app/src/main/java/at/bitfire/davdroid/ui/CreateAddressBookActivity.java
+++ b/app/src/main/java/at/bitfire/davdroid/ui/CreateAddressBookActivity.java
@@ -9,20 +9,25 @@
package at.bitfire.davdroid.ui;
import android.accounts.Account;
+import android.app.Dialog;
+import android.app.ProgressDialog;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
-import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager;
+import android.support.v4.app.NavUtils;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.support.v7.app.AppCompatActivity;
+import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
+import android.widget.EditText;
import android.widget.SimpleAdapter;
import android.widget.Spinner;
import android.widget.Toast;
@@ -78,7 +83,7 @@ public class CreateAddressBookActivity extends AppCompatActivity implements Load
case android.R.id.home:
Intent intent = new Intent(this, AccountActivity.class);
intent.putExtra(AccountActivity.EXTRA_ACCOUNT_NAME, account.name);
- startActivity(intent);
+ NavUtils.navigateUpTo(this, intent);
break;
case R.id.create_address_book:
createAddressBook();
@@ -92,71 +97,158 @@ public class CreateAddressBookActivity extends AppCompatActivity implements Load
protected void createAddressBook() {
Spinner spnrHomeSets = (Spinner)findViewById(R.id.homeset);
HashMap homeSet = (HashMap)spnrHomeSets.getSelectedItem();
-
HttpUrl urlHomeSet = HttpUrl.parse(homeSet.get(ServiceDB.HomeSets.URL));
CollectionInfo info = new CollectionInfo();
- info.url = urlHomeSet.resolve("myAddrBook.vcf").toString();
- info.displayName = "myAddrBook";
-
- new AddressBookCreator().execute( info);
+ boolean ok = true;
+
+ String displayName = ((EditText)findViewById(R.id.title)).getText().toString();
+ if (!TextUtils.isEmpty(displayName))
+ info.displayName = displayName;
+
+ EditText editPathSegment = (EditText)findViewById(R.id.path_segment);
+ String pathSegment = editPathSegment.getText().toString();
+ if (TextUtils.isEmpty(pathSegment)) { // TODO further validations
+ editPathSegment.setError("MUST NOT BE EMPTY");
+ ok = false;
+ } else
+ info.url = urlHomeSet.resolve(pathSegment).toString();
+
+ String description = ((EditText)findViewById(R.id.description)).getText().toString();
+ if (!TextUtils.isEmpty(description))
+ info.description = description;
+
+ if (ok)
+ CreatingAddressBookFragment.newInstance(account, info).show(getSupportFragmentManager(), null);
}
- // AsyncTask for creating the address book
+ public static class CreatingAddressBookFragment extends DialogFragment implements LoaderManager.LoaderCallbacks {
+ protected static final String
+ ARGS_ACCOUNT = "account",
+ ARGS_COLLECTION_INFO = "collectionInfo";
+
+ public static CreatingAddressBookFragment newInstance(Account account, CollectionInfo info) {
+ CreatingAddressBookFragment frag = new CreatingAddressBookFragment();
+ Bundle args = new Bundle(2);
+ args.putParcelable(ARGS_ACCOUNT, account);
+ args.putSerializable(ARGS_COLLECTION_INFO, info);
+ frag.setArguments(args);
+ return frag;
+ }
- class AddressBookCreator extends AsyncTask {
@Override
- protected void onPostExecute(Exception e) {
- String msg = (e == null) ? "Created!" : e.getLocalizedMessage();
- Toast.makeText(CreateAddressBookActivity.this, msg, Toast.LENGTH_LONG).show();
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getLoaderManager().initLoader(0, getArguments(), this);
}
@Override
- protected Exception doInBackground(CollectionInfo[] infoArray) {
- AccountSettings accountSettings = new AccountSettings(CreateAddressBookActivity.this, account);
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dialog = new ProgressDialog.Builder(getActivity())
+ .setTitle(R.string.create_address_book_creating)
+ .setMessage(R.string.please_wait)
+ .setCancelable(false)
+ .create();
+ dialog.setCanceledOnTouchOutside(false);
+ return dialog;
+ }
- CollectionInfo info = infoArray[0];
- OkHttpClient client = HttpClient.create(CreateAddressBookActivity.this);
- client = HttpClient.addAuthentication(client, accountSettings.username(), accountSettings.password(), accountSettings.preemptiveAuth());
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ Account account = (Account)args.getParcelable(ARGS_ACCOUNT);
+ CollectionInfo info = (CollectionInfo)args.getSerializable(ARGS_COLLECTION_INFO);
+ return new AddressBookCreator(getActivity(), account, info);
+ }
- DavResource addressBook = new DavResource(null, client, HttpUrl.parse(info.url));
+ @Override
+ public void onLoadFinished(Loader loader, Exception exception) {
+ dismissAllowingStateLoss();
- StringWriter writer = new StringWriter();
- try {
- XmlSerializer serializer = XmlUtils.newSerializer();
- serializer.setOutput(writer);
- serializer.startDocument("UTF-8", null);
- serializer.setPrefix("", XmlUtils.NS_WEBDAV);
- serializer.setPrefix("CARD", XmlUtils.NS_CARDDAV);
-
- serializer.startTag(XmlUtils.NS_WEBDAV, "mkcol");
- serializer.startTag(XmlUtils.NS_WEBDAV, "set");
- serializer.startTag(XmlUtils.NS_WEBDAV, "prop");
- serializer.startTag(XmlUtils.NS_WEBDAV, "resourcetype");
- serializer.startTag(XmlUtils.NS_WEBDAV, "collection");
- serializer.endTag(XmlUtils.NS_WEBDAV, "collection");
- serializer.startTag(XmlUtils.NS_CARDDAV, "addressbook");
- serializer.endTag(XmlUtils.NS_CARDDAV, "addressbook");
- serializer.endTag(XmlUtils.NS_WEBDAV, "resourcetype");
- serializer.startTag(XmlUtils.NS_WEBDAV, "displayname");
- serializer.text(info.displayName);
- serializer.endTag(XmlUtils.NS_WEBDAV, "displayname");
- serializer.endTag(XmlUtils.NS_WEBDAV, "prop");
- serializer.endTag(XmlUtils.NS_WEBDAV, "set");
- serializer.endTag(XmlUtils.NS_WEBDAV, "mkcol");
- serializer.endDocument();
- } catch (IOException e) {
- Constants.log.error("Couldn't assemble MKCOL request", e);
+ if (exception == null)
+ getActivity().finish();
+ else
+ Toast.makeText(getActivity(), exception.getLocalizedMessage(), Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ }
+
+ protected static class AddressBookCreator extends AsyncTaskLoader {
+ final Account account;
+ final CollectionInfo info;
+ final ServiceDB.OpenHelper dbHelper;
+
+ public AddressBookCreator(Context context, Account account, CollectionInfo collectionInfo) {
+ super(context);
+ this.account = account;
+ info = collectionInfo;
+ dbHelper = new ServiceDB.OpenHelper(context);
}
- String error = null;
- try {
- addressBook.mkCol(writer.toString());
- } catch (IOException|HttpException e) {
- return e;
+ @Override
+ protected void onStartLoading() {
+ forceLoad();
+ }
+
+ @Override
+ public Exception loadInBackground() {
+ OkHttpClient client = HttpClient.create(getContext());
+ client = HttpClient.addAuthentication(client, new AccountSettings(getContext(), account));
+
+ StringWriter writer = new StringWriter();
+ try {
+ XmlSerializer serializer = XmlUtils.newSerializer();
+ serializer.setOutput(writer);
+ serializer.startDocument("UTF-8", null);
+ serializer.setPrefix("", XmlUtils.NS_WEBDAV);
+ serializer.setPrefix("CARD", XmlUtils.NS_CARDDAV);
+
+ serializer.startTag(XmlUtils.NS_WEBDAV, "mkcol");
+ serializer.startTag(XmlUtils.NS_WEBDAV, "set");
+ serializer.startTag(XmlUtils.NS_WEBDAV, "prop");
+ serializer.startTag(XmlUtils.NS_WEBDAV, "resourcetype");
+ serializer.startTag(XmlUtils.NS_WEBDAV, "collection");
+ serializer.endTag(XmlUtils.NS_WEBDAV, "collection");
+ serializer.startTag(XmlUtils.NS_CARDDAV, "addressbook");
+ serializer.endTag(XmlUtils.NS_CARDDAV, "addressbook");
+ serializer.endTag(XmlUtils.NS_WEBDAV, "resourcetype");
+ if (info.displayName != null) {
+ serializer.startTag(XmlUtils.NS_WEBDAV, "displayname");
+ serializer.text(info.displayName);
+ serializer.endTag(XmlUtils.NS_WEBDAV, "displayname");
+ }
+ if (info.description != null) {
+ serializer.startTag(XmlUtils.NS_CARDDAV, "addressbook-description");
+ serializer.text(info.description);
+ serializer.endTag(XmlUtils.NS_CARDDAV, "addressbook-description");
+ }
+ serializer.endTag(XmlUtils.NS_WEBDAV, "prop");
+ serializer.endTag(XmlUtils.NS_WEBDAV, "set");
+ serializer.endTag(XmlUtils.NS_WEBDAV, "mkcol");
+ serializer.endDocument();
+ } catch (IOException e) {
+ Constants.log.error("Couldn't assemble MKCOL request", e);
+ }
+
+ DavResource addressBook = new DavResource(null, client, HttpUrl.parse(info.url));
+ try {
+ addressBook.mkCol(writer.toString());
+
+ // TODO
+ /*SQLiteDatabase db = dbHelper.getWritableDatabase();
+ db.insert(ServiceDB.Collections._TABLE, null, info.toDB());*/
+
+ // TODO add to database
+ } catch (IOException|HttpException e) {
+ return e;
+ } finally {
+ dbHelper.close();
+ }
+ return null;
}
- return null;
}
}
@@ -194,12 +286,11 @@ public class CreateAddressBookActivity extends AppCompatActivity implements Load
private static class AccountLoader extends AsyncTaskLoader {
private final Account account;
- ServiceDB.OpenHelper dbHelper;
+ private final ServiceDB.OpenHelper dbHelper;
public AccountLoader(Context context, Account account) {
super(context);
this.account = account;
-
dbHelper = new ServiceDB.OpenHelper(context);
}
diff --git a/app/src/main/java/at/bitfire/davdroid/ui/CreateCalendarActivity.java b/app/src/main/java/at/bitfire/davdroid/ui/CreateCalendarActivity.java
new file mode 100644
index 00000000..f2983b2c
--- /dev/null
+++ b/app/src/main/java/at/bitfire/davdroid/ui/CreateCalendarActivity.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright © 2013 – 2016 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;
+
+import android.accounts.Account;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.AsyncTaskLoader;
+import android.support.v4.content.Loader;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.AppCompatCheckBox;
+import android.support.v7.widget.AppCompatRadioButton;
+import android.support.v7.widget.AppCompatSpinner;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.webkit.URLUtil;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.Toast;
+
+import net.fortuna.ical4j.data.CalendarBuilder;
+import net.fortuna.ical4j.model.Calendar;
+import net.fortuna.ical4j.model.TimeZoneRegistry;
+import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
+import net.fortuna.ical4j.util.TimeZones;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.UUID;
+
+import at.bitfire.davdroid.Constants;
+import at.bitfire.davdroid.R;
+import at.bitfire.davdroid.model.CollectionInfo;
+import at.bitfire.davdroid.model.ServiceDB;
+import at.bitfire.ical4android.DateUtils;
+import lombok.Cleanup;
+import okhttp3.HttpUrl;
+import yuku.ambilwarna.AmbilWarnaDialog;
+import yuku.ambilwarna.AmbilWarnaSquare;
+
+public class CreateCalendarActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks {
+ public static final String EXTRA_ACCOUNT = "account";
+
+ protected Account account;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ account = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ setContentView(R.layout.activity_create_calendar);
+ final View colorSquare = findViewById(R.id.color);
+ colorSquare.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ new AmbilWarnaDialog(CreateCalendarActivity.this, ((ColorDrawable)colorSquare.getBackground()).getColor(), true, new AmbilWarnaDialog.OnAmbilWarnaListener() {
+ @Override
+ public void onCancel(AmbilWarnaDialog dialog) {
+ }
+
+ @Override
+ public void onOk(AmbilWarnaDialog dialog, int color) {
+ colorSquare.setBackgroundColor(color);
+ }
+ }).show();
+ }
+ });
+
+ getSupportLoaderManager().initLoader(0, null, this);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.activity_create_calendar, menu);
+ return true;
+ }
+
+ public void onCreateCalendar(MenuItem item) {
+ boolean ok = true;
+ CollectionInfo info = new CollectionInfo();
+
+ AppCompatSpinner spinner = (AppCompatSpinner)findViewById(R.id.home_set);
+ String homeSet = (String)spinner.getSelectedItem();
+
+ EditText edit = (EditText)findViewById(R.id.display_name);
+ info.displayName = edit.getText().toString();
+ if (TextUtils.isEmpty(info.displayName)) {
+ edit.setError("Enter a calendar title.");
+ ok = false;
+ }
+
+ edit = (EditText)findViewById(R.id.description);
+ info.description = StringUtils.trimToNull(edit.getText().toString());
+
+ View view = findViewById(R.id.color);
+ info.color = ((ColorDrawable)view.getBackground()).getColor();
+
+ spinner = (AppCompatSpinner)findViewById(R.id.time_zone);
+ net.fortuna.ical4j.model.TimeZone tz = DateUtils.tzRegistry.getTimeZone((String)spinner.getSelectedItem());
+ if (tz != null) {
+ Calendar cal = new Calendar();
+ cal.getComponents().add(tz.getVTimeZone());
+ info.timeZone = cal.toString();
+ }
+
+ RadioGroup typeGroup = (RadioGroup)findViewById(R.id.type);
+ switch (typeGroup.getCheckedRadioButtonId()) {
+ case R.id.type_events:
+ info.supportsVEVENT = true;
+ break;
+ case R.id.type_tasks:
+ info.supportsVTODO = true;
+ break;
+ case R.id.type_events_and_tasks:
+ info.supportsVEVENT = true;
+ info.supportsVTODO = true;
+ break;
+ }
+
+ if (ok) {
+ info.type = CollectionInfo.Type.CALENDAR;
+ info.url = HttpUrl.parse(homeSet).resolve(UUID.randomUUID().toString() + "/").toString();
+ CreateCollectionFragment.newInstance(account, info).show(getSupportFragmentManager(), null);
+ }
+ }
+
+
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ return new AccountInfoLoader(this, account);
+ }
+
+ @Override
+ public void onLoadFinished(Loader loader, AccountInfo info) {
+ AppCompatSpinner spinner = (AppCompatSpinner)findViewById(R.id.time_zone);
+ String[] timeZones = TimeZone.getAvailableIDs();
+ spinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, timeZones));
+ // select system time zone
+ String defaultTimeZone = TimeZone.getDefault().getID();
+ for (int i = 0; i < timeZones.length; i++)
+ if (timeZones[i].equals(defaultTimeZone)) {
+ spinner.setSelection(i);
+ break;
+ }
+
+ if (info != null) {
+ spinner = (AppCompatSpinner)findViewById(R.id.home_set);
+ spinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, info.homeSets));
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ }
+
+ protected static class AccountInfo {
+ List homeSets = new LinkedList<>();
+ }
+
+ protected static class AccountInfoLoader extends AsyncTaskLoader {
+ private final Account account;
+ private final ServiceDB.OpenHelper dbHelper;
+
+ public AccountInfoLoader(Context context, Account account) {
+ super(context);
+ this.account = account;
+ dbHelper = new ServiceDB.OpenHelper(context);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ forceLoad();
+ }
+
+ @Override
+ public AccountInfo loadInBackground() {
+ final AccountInfo info = new AccountInfo();
+
+ // find DAV service and home sets
+ SQLiteDatabase db = dbHelper.getReadableDatabase();
+ try {
+ @Cleanup Cursor cursorService = db.query(ServiceDB.Services._TABLE, new String[] { ServiceDB.Services.ID },
+ ServiceDB.Services.ACCOUNT_NAME + "=? AND " + ServiceDB.Services.SERVICE + "=?",
+ new String[] { account.name, ServiceDB.Services.SERVICE_CALDAV }, null, null, null);
+ if (!cursorService.moveToNext())
+ return null;
+ String strServiceID = cursorService.getString(0);
+
+ @Cleanup Cursor cursorHomeSets = db.query(ServiceDB.HomeSets._TABLE, new String[] { ServiceDB.HomeSets.URL },
+ ServiceDB.HomeSets.SERVICE_ID + "=?", new String[] { strServiceID }, null, null, null);
+ while (cursorHomeSets.moveToNext())
+ info.homeSets.add(cursorHomeSets.getString(0));
+ } finally {
+ dbHelper.close();
+ }
+
+ return info;
+ }
+ }
+}
diff --git a/app/src/main/java/at/bitfire/davdroid/ui/CreateCollectionFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/CreateCollectionFragment.java
new file mode 100644
index 00000000..58e50e62
--- /dev/null
+++ b/app/src/main/java/at/bitfire/davdroid/ui/CreateCollectionFragment.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright © 2013 – 2016 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;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.design.widget.Snackbar;
+import android.support.v4.app.DialogFragment;
+import android.widget.Toast;
+
+import org.apache.commons.lang3.BooleanUtils;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import at.bitfire.dav4android.DavResource;
+import at.bitfire.dav4android.XmlUtils;
+import at.bitfire.dav4android.exception.HttpException;
+import at.bitfire.davdroid.Constants;
+import at.bitfire.davdroid.DavUtils;
+import at.bitfire.davdroid.HttpClient;
+import at.bitfire.davdroid.R;
+import at.bitfire.davdroid.model.CollectionInfo;
+import at.bitfire.davdroid.model.ServiceDB;
+import at.bitfire.davdroid.syncadapter.AccountSettings;
+import at.bitfire.ical4android.DateUtils;
+import lombok.Cleanup;
+import okhttp3.HttpUrl;
+import okhttp3.OkHttpClient;
+
+public class CreateCollectionFragment extends DialogFragment {
+ private static final String
+ ARG_ACCOUNT = "account",
+ ARG_COLLECTION_INFO = "collectionInfo";
+
+ public static CreateCollectionFragment newInstance(Account account, CollectionInfo info) {
+ CreateCollectionFragment frag = new CreateCollectionFragment();
+ Bundle args = new Bundle(2);
+ args.putParcelable(ARG_ACCOUNT, account);
+ args.putSerializable(ARG_COLLECTION_INFO, info);
+ frag.setArguments(args);
+ return frag;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setRetainInstance(true);
+
+ new CreateCollectionTask().execute(getArguments());
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dialog = new ProgressDialog.Builder(getContext())
+ .setTitle(R.string.create_collection_creating)
+ .setMessage(R.string.please_wait)
+ .create();
+ setCancelable(false);
+ return dialog;
+ }
+
+
+ protected class CreateCollectionTask extends AsyncTask {
+ @Override
+ protected Exception doInBackground(Bundle... params) {
+ Bundle args = params[0];
+ Account account = args.getParcelable(ARG_ACCOUNT);
+ CollectionInfo info = (CollectionInfo)args.getSerializable(ARG_COLLECTION_INFO);
+
+ OkHttpClient client = HttpClient.create(getContext());
+ client = HttpClient.addAuthentication(client, new AccountSettings(getContext(), account));
+
+ StringWriter writer = new StringWriter();
+ try {
+ XmlSerializer serializer = XmlUtils.newSerializer();
+ serializer.setOutput(writer);
+ serializer.startDocument("UTF-8", null);
+ serializer.setPrefix("", XmlUtils.NS_WEBDAV);
+ serializer.setPrefix("CAL", XmlUtils.NS_CALDAV);
+ serializer.setPrefix("CARD", XmlUtils.NS_CARDDAV);
+
+ serializer.startTag(XmlUtils.NS_WEBDAV, "mkcol");
+ serializer.startTag(XmlUtils.NS_WEBDAV, "set");
+ serializer.startTag(XmlUtils.NS_WEBDAV, "prop");
+ serializer.startTag(XmlUtils.NS_WEBDAV, "resourcetype");
+ serializer.startTag(XmlUtils.NS_WEBDAV, "collection");
+ serializer.endTag(XmlUtils.NS_WEBDAV, "collection");
+ if (info.type == CollectionInfo.Type.ADDRESS_BOOK) {
+ serializer.startTag(XmlUtils.NS_CARDDAV, "addressbook");
+ serializer.endTag(XmlUtils.NS_CARDDAV, "addressbook");
+ } else if (info.type == CollectionInfo.Type.CALENDAR) {
+ serializer.startTag(XmlUtils.NS_CALDAV, "calendar");
+ serializer.endTag(XmlUtils.NS_CALDAV, "calendar");
+ }
+ serializer.endTag(XmlUtils.NS_WEBDAV, "resourcetype");
+ if (info.displayName != null) {
+ serializer.startTag(XmlUtils.NS_WEBDAV, "displayname");
+ serializer.text(info.displayName);
+ serializer.endTag(XmlUtils.NS_WEBDAV, "displayname");
+ }
+
+ // addressbook-specific properties
+ if (info.type == CollectionInfo.Type.ADDRESS_BOOK) {
+ if (info.description != null) {
+ serializer.startTag(XmlUtils.NS_CARDDAV, "addressbook-description");
+ serializer.text(info.description);
+ serializer.endTag(XmlUtils.NS_CARDDAV, "addressbook-description");
+ }
+ }
+
+ // calendar-specific properties
+ if (info.type == CollectionInfo.Type.CALENDAR) {
+ if (info.description != null) {
+ serializer.startTag(XmlUtils.NS_CALDAV, "calendar-description");
+ serializer.text(info.description);
+ serializer.endTag(XmlUtils.NS_CALDAV, "calendar-description");
+ }
+
+ if (info.color != null) {
+ serializer.startTag(XmlUtils.NS_APPLE_ICAL, "calendar-color");
+ serializer.text(DavUtils.ARGBtoCalDAVColor(info.color));
+ serializer.endTag(XmlUtils.NS_APPLE_ICAL, "calendar-color");
+ }
+
+ if (info.timeZone != null) {
+ serializer.startTag(XmlUtils.NS_CALDAV, "calendar-timezone");
+ serializer.cdsect(info.timeZone);
+ serializer.endTag(XmlUtils.NS_CALDAV, "calendar-timezone");
+ }
+
+ serializer.startTag(XmlUtils.NS_CALDAV, "supported-calendar-component-set");
+ if (BooleanUtils.isTrue(info.supportsVEVENT)) {
+ serializer.startTag(XmlUtils.NS_CALDAV, "comp");
+ serializer.attribute(null, "name", "VEVENT");
+ serializer.endTag(XmlUtils.NS_CALDAV, "comp");
+ }
+ if (BooleanUtils.isTrue(info.supportsVTODO)) {
+ serializer.startTag(XmlUtils.NS_CALDAV, "comp");
+ serializer.attribute(null, "name", "VTODO");
+ serializer.endTag(XmlUtils.NS_CALDAV, "comp");
+ }
+ serializer.endTag(XmlUtils.NS_CALDAV, "supported-calendar-component-set");
+ }
+
+ serializer.endTag(XmlUtils.NS_WEBDAV, "prop");
+ serializer.endTag(XmlUtils.NS_WEBDAV, "set");
+ serializer.endTag(XmlUtils.NS_WEBDAV, "mkcol");
+ serializer.endDocument();
+ } catch (IOException e) {
+ Constants.log.error("Couldn't assemble Extended MKCOL request", e);
+ }
+
+ ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext());
+ DavResource collection = new DavResource(null, client, HttpUrl.parse(info.url));
+ try {
+ // create collection on remote server
+ collection.mkCol(writer.toString());
+
+ // now insert collection into database:
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+
+ // 1. find service ID
+ String serviceType = null;
+ if (info.type == CollectionInfo.Type.ADDRESS_BOOK)
+ serviceType = ServiceDB.Services.SERVICE_CARDDAV;
+ else if (info.type == CollectionInfo.Type.CALENDAR)
+ serviceType = ServiceDB.Services.SERVICE_CALDAV;
+ else
+ throw new IllegalArgumentException("Collection must be an address book or calendar");
+ @Cleanup Cursor c = db.query(ServiceDB.Services._TABLE, new String[] { ServiceDB.Services.ID },
+ ServiceDB.Services.ACCOUNT_NAME + "=? AND " + ServiceDB.Services.SERVICE + "=?",
+ new String[] { account.name, serviceType }, null, null, null
+ );
+ if (!c.moveToNext())
+ throw new IllegalStateException();
+ long serviceID = c.getLong(0);
+
+ // 2. add collection to service
+ ContentValues values = info.toDB();
+ values.put(ServiceDB.Collections.SERVICE_ID, serviceID);
+ db.insert(ServiceDB.Collections._TABLE, null, values);
+ } catch(IOException|HttpException|IllegalStateException e) {
+ return e;
+ } finally {
+ dbHelper.close();
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Exception e) {
+ dismissAllowingStateLoss();
+
+ Activity parent = getActivity();
+ if (parent != null) {
+ if (e != null)
+ Toast.makeText(parent, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
+ else
+ parent.finish();
+ }
+ }
+ }
+
+}
diff --git a/app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.java b/app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.java
index b9d152f9..82ab838c 100644
--- a/app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.java
+++ b/app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.java
@@ -12,6 +12,7 @@ import android.accounts.Account;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
+import android.content.DialogInterface;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.annotation.NonNull;
@@ -19,14 +20,16 @@ import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
+import android.support.v7.app.AlertDialog;
+import android.text.TextUtils;
import android.widget.Toast;
import java.io.IOException;
import at.bitfire.dav4android.DavResource;
import at.bitfire.dav4android.exception.HttpException;
-import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.HttpClient;
+import at.bitfire.davdroid.R;
import at.bitfire.davdroid.model.CollectionInfo;
import at.bitfire.davdroid.model.ServiceDB;
import at.bitfire.davdroid.syncadapter.AccountSettings;
@@ -38,15 +41,6 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa
ARG_ACCOUNT = "account",
ARG_COLLECTION_INFO = "collectionInfo";
- public static DeleteCollectionFragment newInstance(Account account, CollectionInfo collectionInfo) {
- DeleteCollectionFragment frag = new DeleteCollectionFragment();
- Bundle args = new Bundle(2);
- args.putParcelable(ARG_ACCOUNT, account);
- args.putSerializable(ARG_COLLECTION_INFO, collectionInfo);
- frag.setArguments(args);
- return frag;
- }
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -57,11 +51,10 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = new ProgressDialog.Builder(getContext())
- .setTitle("Deleting collection")
- .setMessage("Deleting collection from server, please wait.")
- .setCancelable(false)
+ .setTitle(R.string.delete_collection_deleting_collection)
+ .setMessage(R.string.please_wait)
.create();
- dialog.setCanceledOnTouchOutside(false);
+ setCancelable(false);
return dialog;
}
@@ -79,8 +72,8 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa
@Override
public void onLoadFinished(Loader loader, Exception exception) {
- String msg = (exception == null) ? "Collection deleted" : exception.getLocalizedMessage();
- Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show();
+ if (exception != null)
+ Toast.makeText(getContext(), exception.getLocalizedMessage(), Toast.LENGTH_LONG).show();
dismissAllowingStateLoss();
AccountActivity activity = (AccountActivity)getActivity();
@@ -113,23 +106,64 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa
@Override
public Exception loadInBackground() {
- SQLiteDatabase db = dbHelper.getReadableDatabase();
-
OkHttpClient httpClient = HttpClient.create(getContext());
httpClient = HttpClient.addAuthentication(httpClient, new AccountSettings(getContext(), account));
DavResource collection = new DavResource(null, httpClient, url);
try {
+ // delete collection from server
collection.delete(null);
+ // delete collection locally
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete(ServiceDB.Collections._TABLE, ServiceDB.Collections.ID + "=?", new String[] { String.valueOf(collectionId) });
return null;
} catch (IOException|HttpException e) {
return e;
} finally {
- db.close();
+ dbHelper.close();
}
}
}
+
+
+ public static class ConfirmDeleteCollectionFragment extends DialogFragment {
+
+ public static ConfirmDeleteCollectionFragment newInstance(Account account, CollectionInfo collectionInfo) {
+ ConfirmDeleteCollectionFragment frag = new ConfirmDeleteCollectionFragment();
+ Bundle args = new Bundle(2);
+ args.putParcelable(ARG_ACCOUNT, account);
+ args.putSerializable(ARG_COLLECTION_INFO, collectionInfo);
+ frag.setArguments(args);
+ return frag;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ CollectionInfo collectionInfo = (CollectionInfo)getArguments().getSerializable(ARG_COLLECTION_INFO);
+ String name = TextUtils.isEmpty(collectionInfo.displayName) ? collectionInfo.url : collectionInfo.displayName;
+
+ return new AlertDialog.Builder(getContext())
+ .setTitle(R.string.delete_collection_confirm_title)
+ .setMessage(getString(R.string.delete_collection_confirm_warning, name))
+ .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ DialogFragment frag = new DeleteCollectionFragment();
+ frag.setArguments(getArguments());
+ frag.show(getFragmentManager(), null);
+ }
+ })
+ .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dismiss();
+ }
+ })
+ .create();
+ }
+ }
+
}
diff --git a/app/src/main/res/drawable/ic_menu_light.xml b/app/src/main/res/drawable/ic_menu_light.xml
new file mode 100644
index 00000000..811eef7c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu_light.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_create_address_book.xml b/app/src/main/res/layout/activity_create_address_book.xml
index b5c774f3..bb95d179 100644
--- a/app/src/main/res/layout/activity_create_address_book.xml
+++ b/app/src/main/res/layout/activity_create_address_book.xml
@@ -22,4 +22,22 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_create_calendar.xml b/app/src/main/res/layout/activity_create_calendar.xml
new file mode 100644
index 00000000..a695bdec
--- /dev/null
+++ b/app/src/main/res/layout/activity_create_calendar.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/activity_create_calendar.xml b/app/src/main/res/menu/activity_create_calendar.xml
new file mode 100644
index 00000000..4de60523
--- /dev/null
+++ b/app/src/main/res/menu/activity_create_calendar.xml
@@ -0,0 +1,17 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5daee6b1..19ed8a6e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -13,7 +13,7 @@
DAVdroid
Help
Manage accounts
- Please wait
+ Please wait …
Send
Skip
@@ -170,8 +170,14 @@
- Once a day
-
+
Create address book
+ Creating address book
+ Create calendar
+ Creating collection
+ Are you sure?
+ This collection (%s) and all its entries from the server will be removed from the server.
+ Deleting collection
Android version update
Android version updates may have an impact on how DAVdroid works. If there are problems, please delete your DAVdroid accounts and add them again.
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 1acc47e6..a80afb1a 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -92,10 +92,10 @@
-
+
\ No newline at end of file
diff --git a/dav4android b/dav4android
index 0e9522bb..fd19c653 160000
--- a/dav4android
+++ b/dav4android
@@ -1 +1 @@
-Subproject commit 0e9522bb3a2d4e33bac214883f8316b91be0960e
+Subproject commit fd19c6531ad5c1cbe210a7e70f5781cbfd5744a7