diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 24c5405e..072c30ad 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -127,15 +127,21 @@
             android:name=".DavService"
             android:enabled="true">
         </service>
-        <receiver
-            android:name=".AccountsChangedReceiver"
-            android:enabled="true"
-            android:exported="true">
+
+        <receiver android:name=".AccountsChangedReceiver">
             <intent-filter>
                 <action android:name="android.accounts.LOGIN_ACCOUNTS_CHANGED"/>
             </intent-filter>
         </receiver>
-        
+
+        <receiver android:name=".PackageChangedReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.PACKAGE_ADDED"/>
+                <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED"/>
+                <data android:scheme="package"/>
+            </intent-filter>
+        </receiver>
+
         <activity
             android:name=".ui.AccountsActivity"
             android:label="@string/app_name"
diff --git a/app/src/main/java/at/bitfire/davdroid/Constants.java b/app/src/main/java/at/bitfire/davdroid/Constants.java
index 5adc720c..22543d17 100644
--- a/app/src/main/java/at/bitfire/davdroid/Constants.java
+++ b/app/src/main/java/at/bitfire/davdroid/Constants.java
@@ -26,4 +26,6 @@ public class Constants {
 
     public static final Uri webUri = Uri.parse("https://davdroid.bitfire.at/?pk_campaign=davdroid-app");
 
+    public static final int DEFAULT_SYNC_INTERVAL = 4 * 3600;  // 4 hours
+
 }
diff --git a/app/src/main/java/at/bitfire/davdroid/PackageChangedReceiver.java b/app/src/main/java/at/bitfire/davdroid/PackageChangedReceiver.java
new file mode 100644
index 00000000..dd076a59
--- /dev/null
+++ b/app/src/main/java/at/bitfire/davdroid/PackageChangedReceiver.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright © Ricki Hirner (bitfire web engineering).
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Public License v3.0
+ * which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/gpl.html
+ */
+
+package at.bitfire.davdroid;
+
+import android.accounts.Account;
+import android.annotation.SuppressLint;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import at.bitfire.davdroid.model.ServiceDB;
+import at.bitfire.davdroid.model.ServiceDB.Services;
+import at.bitfire.davdroid.resource.LocalTaskList;
+import at.bitfire.ical4android.TaskProvider;
+import lombok.Cleanup;
+
+public class PackageChangedReceiver extends BroadcastReceiver {
+
+    @Override
+    @SuppressLint("MissingPermission")
+    public void onReceive(Context context, Intent intent) {
+        if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction()) ||
+            Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(intent.getAction())) {
+
+            boolean tasksInstalled = LocalTaskList.tasksProviderAvailable(context);
+            App.log.info("Package (un)installed; OpenTasks provider now available = " + tasksInstalled);
+
+            // check all accounts and (de)activate OpenTasks if a CalDAV service is defined
+            @Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(context);
+            SQLiteDatabase db = dbHelper.getReadableDatabase();
+
+            @Cleanup Cursor cursor = db.query(Services._TABLE, new String[] { Services.ACCOUNT_NAME },
+                    Services.SERVICE + "=?", new String[] { Services.SERVICE_CALDAV }, null, null, null);
+            while (cursor.moveToNext()) {
+                Account account = new Account(cursor.getString(0), Constants.ACCOUNT_TYPE);
+
+                if (tasksInstalled) {
+                    ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1);
+                    try {
+                        AccountSettings settings = new AccountSettings(context, account);
+                        settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Constants.DEFAULT_SYNC_INTERVAL);
+                    } catch(InvalidAccountException ignored) {
+                    }
+                } else
+                    ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 0);
+
+            }
+
+        }
+    }
+
+}
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 a6b1414f..b293a7fa 100644
--- a/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.java
+++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.java
@@ -45,9 +45,6 @@ public class LocalTaskList extends AndroidTaskList implements LocalCollection {
             LocalTask.COLUMN_ETAG
     };
 
-    // can be cached, because after installing OpenTasks, you have to re-install DAVdroid anyway
-    private static Boolean tasksProviderAvailable;
-
 
     @Override
     protected String[] taskBaseInfoColumns() {
@@ -140,15 +137,11 @@ public class LocalTaskList extends AndroidTaskList implements LocalCollection {
     // helpers
 
     public static boolean tasksProviderAvailable(@NonNull Context context) {
-        if (tasksProviderAvailable != null)
-            return tasksProviderAvailable;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+            return context.getPackageManager().resolveContentProvider(TaskProvider.ProviderName.OpenTasks.authority, 0) != null;
         else {
-            if (Build.VERSION.SDK_INT >= 23)
-                return context.getPackageManager().resolveContentProvider(TaskProvider.ProviderName.OpenTasks.authority, 0) != null;
-            else {
-                @Cleanup TaskProvider provider = TaskProvider.acquire(context.getContentResolver(), TaskProvider.ProviderName.OpenTasks);
-                return tasksProviderAvailable = (provider != null);
-            }
+            @Cleanup TaskProvider provider = TaskProvider.acquire(context.getContentResolver(), TaskProvider.ProviderName.OpenTasks);
+            return provider != null;
         }
     }
 
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 7a5fb9b7..b2d787b6 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
@@ -15,7 +15,6 @@ import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Intent;
 import android.database.sqlite.SQLiteDatabase;
-import android.os.Build;
 import android.os.Bundle;
 import android.provider.CalendarContract;
 import android.provider.ContactsContract;
@@ -50,7 +49,6 @@ import lombok.Cleanup;
 public class AccountDetailsFragment extends Fragment {
 
     private static final String KEY_CONFIG = "config";
-    private static final int DEFAULT_SYNC_INTERVAL = 4 * 3600;  // 4 hours
 
     Spinner spnrGroupMethod;
 
@@ -141,7 +139,7 @@ public class AccountDetailsFragment extends Fragment {
 
                 // enable contact sync
                 ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
-                settings.setSyncInterval(ContactsContract.AUTHORITY, DEFAULT_SYNC_INTERVAL);
+                settings.setSyncInterval(ContactsContract.AUTHORITY, Constants.DEFAULT_SYNC_INTERVAL);
             } else
                 // disable contact sync when CardDAV is not available
                 ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);
@@ -156,26 +154,16 @@ public class AccountDetailsFragment extends Fragment {
 
                 // enable calendar sync
                 ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 1);
-                settings.setSyncInterval(CalendarContract.AUTHORITY, DEFAULT_SYNC_INTERVAL);
+                settings.setSyncInterval(CalendarContract.AUTHORITY, Constants.DEFAULT_SYNC_INTERVAL);
 
-                // enable task sync, if possible
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
-                    /* Android >=6, it's possible to gain OpenTasks permissions dynamically, so
-                     * OpenTasks sync will be enabled by default. Setting the sync interval to "manually"
-                     * if OpenTasks is not installed avoids the "sync error" in Android settings / Accounts. */
+                // enable task sync if OpenTasks is installed
+                // further changes will be handled by PackageChangedReceiver
+                if (LocalTaskList.tasksProviderAvailable(getContext())) {
                     ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1);
-                    settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority,
-                            LocalTaskList.tasksProviderAvailable(getContext()) ? DEFAULT_SYNC_INTERVAL : AccountSettings.SYNC_INTERVAL_MANUALLY);
-                } else {
-                    // Android <6: enable task sync according to whether OpenTasks is accessible
-                    if (LocalTaskList.tasksProviderAvailable(getContext())) {
-                        ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 1);
-                        settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, DEFAULT_SYNC_INTERVAL);
-                    } else
-                        // Android <6 only: disable OpenTasks sync forever when OpenTasks is not installed
-                        // because otherwise, there will be a non-catchable SecurityException as soon as OpenTasks is installed
-                        ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 0);
-                }
+                    settings.setSyncInterval(TaskProvider.ProviderName.OpenTasks.authority, Constants.DEFAULT_SYNC_INTERVAL);
+                } else
+                    ContentResolver.setIsSyncable(account, TaskProvider.ProviderName.OpenTasks.authority, 0);
+
             } else {
                 // disable calendar and task sync when CalDAV is not available
                 ContentResolver.setIsSyncable(account, CalendarContract.AUTHORITY, 0);
diff --git a/app/src/main/res/xml/sync_calendars.xml b/app/src/main/res/xml/sync_calendars.xml
index 7adc91e9..1579537e 100644
--- a/app/src/main/res/xml/sync_calendars.xml
+++ b/app/src/main/res/xml/sync_calendars.xml
@@ -11,5 +11,4 @@
     android:contentAuthority="com.android.calendar"
     android:allowParallelSyncs="true"
     android:supportsUploading="true"
-    android:isAlwaysSyncable="true"
     android:userVisible="true" />
diff --git a/app/src/main/res/xml/sync_contacts.xml b/app/src/main/res/xml/sync_contacts.xml
index 1cd55f2f..ceb54f49 100644
--- a/app/src/main/res/xml/sync_contacts.xml
+++ b/app/src/main/res/xml/sync_contacts.xml
@@ -11,5 +11,4 @@
     android:contentAuthority="com.android.contacts"
     android:allowParallelSyncs="true"
     android:supportsUploading="true"
-    android:isAlwaysSyncable="true"
     android:userVisible="true" />
diff --git a/app/src/main/res/xml/sync_tasks.xml b/app/src/main/res/xml/sync_tasks.xml
index 76c6866a..1c2ec6db 100644
--- a/app/src/main/res/xml/sync_tasks.xml
+++ b/app/src/main/res/xml/sync_tasks.xml
@@ -11,5 +11,4 @@
     android:contentAuthority="org.dmfs.tasks"
     android:allowParallelSyncs="true"
     android:supportsUploading="true"
-    android:isAlwaysSyncable="true"
     android:userVisible="true" />