mirror of
https://github.com/bitfireAT/davx5-ose
synced 2024-07-22 11:11:02 +00:00
Better handling of tasks app changes (#652)
* Fix task sync update when new app is selected by user or TasksWatcher * Minor refactoring, KDoc
This commit is contained in:
parent
322a7565b0
commit
3ab278a315
|
@ -61,7 +61,7 @@ class App: Application(), Thread.UncaughtExceptionHandler, Configuration.Provide
|
|||
// watch for account changes/deletions
|
||||
accountsUpdatedListener.listen()
|
||||
|
||||
// watch storage because low storage means synchronization is stopped
|
||||
// watch storage because low storage means sync framework stops local content update notifications
|
||||
storageLowReceiver.listen()
|
||||
|
||||
// watch installed/removed tasks apps and update sync settings accordingly
|
||||
|
|
|
@ -6,8 +6,7 @@ package at.bitfire.davdroid
|
|||
|
||||
import android.content.Context
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.resource.TaskUtils
|
||||
import at.bitfire.davdroid.syncadapter.SyncUtils
|
||||
import at.bitfire.davdroid.util.TaskUtils
|
||||
import at.bitfire.ical4android.TaskProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -31,28 +30,26 @@ class TasksWatcher private constructor(
|
|||
|
||||
override fun onPackageChanged() {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
if (TaskUtils.currentProvider(context) == null) {
|
||||
/* Currently no usable tasks provider.
|
||||
Iterate through all supported providers and select one, if available. */
|
||||
val currentProvider = TaskUtils.currentProvider(context)
|
||||
Logger.log.info("App launched or package (un)installed; current tasks provider = $currentProvider")
|
||||
|
||||
if (currentProvider == null) {
|
||||
// Iterate through all supported providers and select one, if available.
|
||||
var providerSelected = false
|
||||
for (provider in TaskProvider.ProviderName.entries) {
|
||||
val available = context.packageManager.resolveContentProvider(provider.authority, 0) != null
|
||||
if (available) {
|
||||
Logger.log.info("Selecting new tasks provider: $provider")
|
||||
TaskUtils.selectProvider(context, provider, updateSyncSettings = false)
|
||||
TaskUtils.selectProvider(context, provider)
|
||||
providerSelected = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!providerSelected)
|
||||
// no provider available, also clear setting
|
||||
TaskUtils.selectProvider(context, null, updateSyncSettings = false)
|
||||
// no provider available (anymore), also clear setting and sync
|
||||
TaskUtils.selectProvider(context, null)
|
||||
}
|
||||
|
||||
// update sync settings
|
||||
SyncUtils.updateTaskSync(context)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
/***************************************************************************************************
|
||||
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
||||
**************************************************************************************************/
|
||||
|
||||
package at.bitfire.davdroid.resource
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.map
|
||||
import at.bitfire.davdroid.settings.Settings
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.syncadapter.SyncUtils
|
||||
import at.bitfire.ical4android.TaskProvider.ProviderName
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
object TaskUtils {
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface TaskUtilsEntryPoint {
|
||||
fun settingsManager(): SettingsManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently selected tasks provider (if it's still available = installed).
|
||||
*
|
||||
* @return the currently selected tasks provider, or null if none is available
|
||||
*/
|
||||
fun currentProvider(context: Context): ProviderName? {
|
||||
val settingsManager = EntryPointAccessors.fromApplication(context, TaskUtilsEntryPoint::class.java).settingsManager()
|
||||
val preferredAuthority = settingsManager.getString(Settings.SELECTED_TASKS_PROVIDER) ?: return null
|
||||
return preferredAuthorityToProviderName(preferredAuthority, context.packageManager)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently selected tasks provider (if it's still available = installed).
|
||||
*
|
||||
* @return the currently selected tasks provider, or null if none is available
|
||||
*/
|
||||
fun currentProviderLive(context: Context): LiveData<ProviderName?> {
|
||||
val settingsManager = EntryPointAccessors.fromApplication(context, TaskUtilsEntryPoint::class.java).settingsManager()
|
||||
return settingsManager.getStringLive(Settings.SELECTED_TASKS_PROVIDER).map { preferred ->
|
||||
if (preferred != null)
|
||||
preferredAuthorityToProviderName(preferred, context.packageManager)
|
||||
else
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun preferredAuthorityToProviderName(
|
||||
preferredAuthority: String,
|
||||
packageManager: PackageManager
|
||||
): ProviderName? {
|
||||
ProviderName.entries.toTypedArray()
|
||||
.sortedByDescending { it.authority == preferredAuthority }
|
||||
.forEach { providerName ->
|
||||
if (packageManager.resolveContentProvider(providerName.authority, 0) != null)
|
||||
return providerName
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun isAvailable(context: Context) = currentProvider(context) != null
|
||||
|
||||
fun selectProvider(context: Context, providerName: ProviderName?, updateSyncSettings: Boolean = false) {
|
||||
val settingsManager = EntryPointAccessors.fromApplication(context, TaskUtilsEntryPoint::class.java).settingsManager()
|
||||
settingsManager.putString(Settings.SELECTED_TASKS_PROVIDER, providerName?.authority)
|
||||
|
||||
if (updateSyncSettings)
|
||||
SyncUtils.updateTaskSync(context)
|
||||
}
|
||||
|
||||
}
|
|
@ -22,7 +22,7 @@ import at.bitfire.davdroid.db.Service
|
|||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.resource.LocalAddressBook
|
||||
import at.bitfire.davdroid.resource.LocalTask
|
||||
import at.bitfire.davdroid.resource.TaskUtils
|
||||
import at.bitfire.davdroid.util.TaskUtils
|
||||
import at.bitfire.davdroid.syncadapter.BaseSyncWorker
|
||||
import at.bitfire.davdroid.syncadapter.SyncUtils
|
||||
import at.bitfire.davdroid.util.setAndVerifyUserData
|
||||
|
@ -420,7 +420,7 @@ class AccountSettingsMigrations(
|
|||
@Suppress("unused")
|
||||
private fun update_4_5() {
|
||||
// call PackageChangedReceiver which then enables/disables OpenTasks sync when it's (not) available
|
||||
SyncUtils.updateTaskSync(context)
|
||||
TaskUtils.selectProvider(context, TaskUtils.currentProvider(context))
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
|
|
|
@ -8,8 +8,6 @@ import androidx.appcompat.app.AppCompatDelegate
|
|||
|
||||
object Settings {
|
||||
|
||||
const val BATTERY_OPTIMIZATION = "battery_optimization"
|
||||
|
||||
const val DISTRUST_SYSTEM_CERTIFICATES = "distrust_system_certs"
|
||||
|
||||
const val PROXY_TYPE = "proxy_type" // Integer
|
||||
|
@ -43,7 +41,7 @@ object Settings {
|
|||
* Selected tasks app. When at least one tasks app is installed, this setting is set to its authority.
|
||||
* In case of multiple available tasks app, the user can choose one and this setting will reflect the selected one.
|
||||
*
|
||||
* If no tasks app is installed, this setting is not set.
|
||||
* If no tasks app is available, this setting is not set.
|
||||
*/
|
||||
const val SELECTED_TASKS_PROVIDER = "preferred_tasks_provider"
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import at.bitfire.davdroid.db.Service
|
|||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.network.HttpClient
|
||||
import at.bitfire.davdroid.resource.LocalJtxCollection
|
||||
import at.bitfire.davdroid.util.TaskUtils
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.ical4android.JtxCollection
|
||||
import at.bitfire.ical4android.TaskProvider
|
||||
|
@ -62,7 +63,7 @@ class JtxSyncer(context: Context): Syncer(context) {
|
|||
}
|
||||
|
||||
} catch (e: TaskProvider.ProviderTooOldException) {
|
||||
SyncUtils.notifyProviderTooOld(context, e)
|
||||
TaskUtils.notifyProviderTooOld(context, e)
|
||||
} catch (e: Exception) {
|
||||
Logger.log.log(Level.SEVERE, "Couldn't sync jtx collections", e)
|
||||
}
|
||||
|
|
|
@ -4,35 +4,14 @@
|
|||
|
||||
package at.bitfire.davdroid.syncadapter
|
||||
|
||||
import android.accounts.Account
|
||||
import android.accounts.AccountManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.net.Uri
|
||||
import android.provider.CalendarContract
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import at.bitfire.davdroid.InvalidAccountException
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.db.AppDatabase
|
||||
import at.bitfire.davdroid.db.Service
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.resource.TaskUtils
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.davdroid.settings.Settings
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.ui.NotificationUtils
|
||||
import at.bitfire.davdroid.ui.NotificationUtils.notifyIfPossible
|
||||
import at.bitfire.davdroid.util.PermissionUtils
|
||||
import at.bitfire.ical4android.TaskProvider
|
||||
import at.bitfire.davdroid.util.TaskUtils
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
/**
|
||||
|
@ -47,43 +26,6 @@ object SyncUtils {
|
|||
fun settingsManager(): SettingsManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an Intent and redirects the user to the package in the market to update the app
|
||||
*
|
||||
* @param e the TaskProvider.ProviderTooOldException to be shown
|
||||
*/
|
||||
fun notifyProviderTooOld(context: Context, e: TaskProvider.ProviderTooOldException) {
|
||||
val nm = NotificationManagerCompat.from(context)
|
||||
val message = context.getString(R.string.sync_error_tasks_required_version, e.provider.minVersionName)
|
||||
|
||||
val pm = context.packageManager
|
||||
val tasksAppInfo = pm.getPackageInfo(e.provider.packageName, 0)
|
||||
val tasksAppLabel = tasksAppInfo.applicationInfo.loadLabel(pm)
|
||||
|
||||
val notify = NotificationUtils.newBuilder(context, NotificationUtils.CHANNEL_SYNC_ERRORS)
|
||||
.setSmallIcon(R.drawable.ic_sync_problem_notify)
|
||||
.setContentTitle(context.getString(R.string.sync_error_tasks_too_old, tasksAppLabel))
|
||||
.setContentText(message)
|
||||
.setSubText("$tasksAppLabel ${e.installedVersionName}")
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
|
||||
try {
|
||||
val icon = pm.getApplicationIcon(e.provider.packageName)
|
||||
if (icon is BitmapDrawable)
|
||||
notify.setLargeIcon(icon.bitmap)
|
||||
} catch (ignored: PackageManager.NameNotFoundException) {
|
||||
// couldn't get provider app icon
|
||||
}
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=${e.provider.packageName}"))
|
||||
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
|
||||
if (intent.resolveActivity(pm) != null)
|
||||
notify.setContentIntent(PendingIntent.getActivity(context, 0, intent, flags))
|
||||
|
||||
nm.notifyIfPossible(NotificationUtils.NOTIFY_TASKS_PROVIDER_TOO_OLD, notify.build())
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all available sync authorities:
|
||||
*
|
||||
|
@ -107,61 +49,4 @@ object SyncUtils {
|
|||
return result
|
||||
}
|
||||
|
||||
// task sync utils
|
||||
|
||||
/**
|
||||
* Sets up sync for the current TaskProvider (and disables sync for unavailable task providers).
|
||||
*
|
||||
* In case of missing permissions, a notification is shown.
|
||||
*/
|
||||
@WorkerThread
|
||||
fun updateTaskSync(context: Context) {
|
||||
val currentProvider = TaskUtils.currentProvider(context)
|
||||
Logger.log.info("App launched or other package (un)installed; current tasks provider = $currentProvider")
|
||||
|
||||
var permissionsRequired = false // whether additional permissions are required
|
||||
|
||||
// check all accounts and (de)activate task provider(s) if a CalDAV service is defined
|
||||
val db = EntryPointAccessors.fromApplication(context, SyncUtilsEntryPoint::class.java).appDatabase()
|
||||
val accountManager = AccountManager.get(context)
|
||||
for (account in accountManager.getAccountsByType(context.getString(R.string.account_type))) {
|
||||
val hasCalDAV = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) != null
|
||||
for (providerName in TaskProvider.ProviderName.entries) {
|
||||
val isSyncable = ContentResolver.getIsSyncable(account, providerName.authority) // may be -1 (unknown state)
|
||||
val shallBeSyncable = hasCalDAV && providerName == currentProvider
|
||||
if ((shallBeSyncable && isSyncable != 1) || (!shallBeSyncable && isSyncable != 0)) {
|
||||
// enable/disable sync
|
||||
setSyncableFromSettings(context, account, providerName.authority, shallBeSyncable)
|
||||
|
||||
// if sync has just been enabled: check whether additional permissions are required
|
||||
if (shallBeSyncable && !PermissionUtils.havePermissions(context, providerName.permissions))
|
||||
permissionsRequired = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (permissionsRequired) {
|
||||
Logger.log.warning("Tasks synchronization is now enabled for at least one account, but permissions are not granted")
|
||||
PermissionUtils.notifyPermissions(context, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSyncableFromSettings(context: Context, account: Account, authority: String, syncable: Boolean) {
|
||||
val settingsManager by lazy { EntryPointAccessors.fromApplication(context, SyncUtilsEntryPoint::class.java).settingsManager() }
|
||||
if (syncable) {
|
||||
Logger.log.info("Enabling $authority sync for $account")
|
||||
ContentResolver.setIsSyncable(account, authority, 1)
|
||||
try {
|
||||
val settings = AccountSettings(context, account)
|
||||
val interval = settings.getTasksSyncInterval() ?: settingsManager.getLong(Settings.DEFAULT_SYNC_INTERVAL)
|
||||
settings.setSyncInterval(authority, interval)
|
||||
} catch (e: InvalidAccountException) {
|
||||
// account has already been removed
|
||||
}
|
||||
} else {
|
||||
Logger.log.info("Disabling $authority sync for $account")
|
||||
ContentResolver.setIsSyncable(account, authority, 0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -15,6 +15,7 @@ import at.bitfire.davdroid.db.Service
|
|||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.network.HttpClient
|
||||
import at.bitfire.davdroid.resource.LocalTaskList
|
||||
import at.bitfire.davdroid.util.TaskUtils
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.ical4android.DmfsTaskList
|
||||
import at.bitfire.ical4android.TaskProvider
|
||||
|
@ -61,7 +62,7 @@ class TaskSyncer(context: Context): Syncer(context) {
|
|||
TasksSyncManager(context, account, accountSettings, httpClient.value, extras, authority, syncResult, taskList).performSync()
|
||||
}
|
||||
} catch (e: TaskProvider.ProviderTooOldException) {
|
||||
SyncUtils.notifyProviderTooOld(context, e)
|
||||
TaskUtils.notifyProviderTooOld(context, e)
|
||||
syncResult.databaseError = true
|
||||
} catch (e: Exception) {
|
||||
Logger.log.log(Level.SEVERE, "Couldn't sync task lists", e)
|
||||
|
|
|
@ -63,7 +63,7 @@ import at.bitfire.cert4android.CustomCertStore
|
|||
import at.bitfire.davdroid.BuildConfig
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.resource.TaskUtils
|
||||
import at.bitfire.davdroid.util.TaskUtils
|
||||
import at.bitfire.davdroid.settings.Settings
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsPage
|
||||
|
|
|
@ -51,7 +51,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||
import at.bitfire.davdroid.BuildConfig
|
||||
import at.bitfire.davdroid.PackageChangedReceiver
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.resource.TaskUtils
|
||||
import at.bitfire.davdroid.util.TaskUtils
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.ui.UiUtils.toAnnotatedString
|
||||
import at.bitfire.davdroid.ui.widget.CardWithImage
|
||||
|
|
|
@ -75,7 +75,7 @@ import androidx.paging.compose.LazyPagingItems
|
|||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.db.Collection
|
||||
import at.bitfire.davdroid.resource.TaskUtils
|
||||
import at.bitfire.davdroid.util.TaskUtils
|
||||
import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.davdroid.syncadapter.OneTimeSyncWorker
|
||||
|
|
|
@ -35,7 +35,7 @@ import at.bitfire.davdroid.log.Logger
|
|||
import at.bitfire.davdroid.network.HttpClient
|
||||
import at.bitfire.davdroid.resource.LocalAddressBook
|
||||
import at.bitfire.davdroid.resource.LocalTaskList
|
||||
import at.bitfire.davdroid.resource.TaskUtils
|
||||
import at.bitfire.davdroid.util.TaskUtils
|
||||
import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.davdroid.syncadapter.AccountsCleanupWorker
|
||||
|
|
|
@ -63,7 +63,7 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.db.Credentials
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.resource.TaskUtils
|
||||
import at.bitfire.davdroid.util.TaskUtils
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.syncadapter.OneTimeSyncWorker
|
||||
|
|
|
@ -7,7 +7,7 @@ package at.bitfire.davdroid.ui.intro
|
|||
import android.app.Application
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import at.bitfire.davdroid.resource.TaskUtils
|
||||
import at.bitfire.davdroid.util.TaskUtils
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.ui.TasksActivity
|
||||
import at.bitfire.davdroid.ui.TasksCard
|
||||
|
|
|
@ -32,7 +32,7 @@ import at.bitfire.davdroid.db.Credentials
|
|||
import at.bitfire.davdroid.db.HomeSet
|
||||
import at.bitfire.davdroid.db.Service
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.resource.TaskUtils
|
||||
import at.bitfire.davdroid.util.TaskUtils
|
||||
import at.bitfire.davdroid.servicedetection.DavResourceFinder
|
||||
import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
|
|
200
app/src/main/kotlin/at/bitfire/davdroid/util/TaskUtils.kt
Normal file
200
app/src/main/kotlin/at/bitfire/davdroid/util/TaskUtils.kt
Normal file
|
@ -0,0 +1,200 @@
|
|||
/***************************************************************************************************
|
||||
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
||||
**************************************************************************************************/
|
||||
|
||||
package at.bitfire.davdroid.util
|
||||
|
||||
import android.accounts.Account
|
||||
import android.accounts.AccountManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.net.Uri
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.map
|
||||
import at.bitfire.davdroid.InvalidAccountException
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.db.Service
|
||||
import at.bitfire.davdroid.log.Logger
|
||||
import at.bitfire.davdroid.settings.AccountSettings
|
||||
import at.bitfire.davdroid.settings.Settings
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.syncadapter.PeriodicSyncWorker
|
||||
import at.bitfire.davdroid.syncadapter.SyncUtils
|
||||
import at.bitfire.davdroid.ui.NotificationUtils
|
||||
import at.bitfire.davdroid.ui.NotificationUtils.notifyIfPossible
|
||||
import at.bitfire.ical4android.TaskProvider
|
||||
import at.bitfire.ical4android.TaskProvider.ProviderName
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
object TaskUtils {
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface TaskUtilsEntryPoint {
|
||||
fun settingsManager(): SettingsManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently selected tasks provider (if it's still available = installed).
|
||||
*
|
||||
* @return the currently selected tasks provider, or null if none is available
|
||||
*/
|
||||
fun currentProvider(context: Context): ProviderName? {
|
||||
val settingsManager = EntryPointAccessors.fromApplication(context, TaskUtilsEntryPoint::class.java).settingsManager()
|
||||
val preferredAuthority = settingsManager.getString(Settings.SELECTED_TASKS_PROVIDER) ?: return null
|
||||
return preferredAuthorityToProviderName(preferredAuthority, context.packageManager)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently selected tasks provider (if it's still available = installed).
|
||||
*
|
||||
* @return the currently selected tasks provider, or null if none is available
|
||||
*/
|
||||
fun currentProviderLive(context: Context): LiveData<ProviderName?> {
|
||||
val settingsManager = EntryPointAccessors.fromApplication(context, TaskUtilsEntryPoint::class.java).settingsManager()
|
||||
return settingsManager.getStringLive(Settings.SELECTED_TASKS_PROVIDER).map { preferred ->
|
||||
if (preferred != null)
|
||||
preferredAuthorityToProviderName(preferred, context.packageManager)
|
||||
else
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun preferredAuthorityToProviderName(
|
||||
preferredAuthority: String,
|
||||
packageManager: PackageManager
|
||||
): ProviderName? {
|
||||
ProviderName.entries.toTypedArray()
|
||||
.sortedByDescending { it.authority == preferredAuthority }
|
||||
.forEach { providerName ->
|
||||
if (packageManager.resolveContentProvider(providerName.authority, 0) != null)
|
||||
return providerName
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun isAvailable(context: Context) = currentProvider(context) != null
|
||||
|
||||
/**
|
||||
* Starts an Intent and redirects the user to the package in the market to update the app
|
||||
*
|
||||
* @param e the TaskProvider.ProviderTooOldException to be shown
|
||||
*/
|
||||
fun notifyProviderTooOld(context: Context, e: TaskProvider.ProviderTooOldException) {
|
||||
val nm = NotificationManagerCompat.from(context)
|
||||
val message = context.getString(R.string.sync_error_tasks_required_version, e.provider.minVersionName)
|
||||
|
||||
val pm = context.packageManager
|
||||
val tasksAppInfo = pm.getPackageInfo(e.provider.packageName, 0)
|
||||
val tasksAppLabel = tasksAppInfo.applicationInfo.loadLabel(pm)
|
||||
|
||||
val notify = NotificationUtils.newBuilder(context, NotificationUtils.CHANNEL_SYNC_ERRORS)
|
||||
.setSmallIcon(R.drawable.ic_sync_problem_notify)
|
||||
.setContentTitle(context.getString(R.string.sync_error_tasks_too_old, tasksAppLabel))
|
||||
.setContentText(message)
|
||||
.setSubText("$tasksAppLabel ${e.installedVersionName}")
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
|
||||
try {
|
||||
val icon = pm.getApplicationIcon(e.provider.packageName)
|
||||
if (icon is BitmapDrawable)
|
||||
notify.setLargeIcon(icon.bitmap)
|
||||
} catch (ignored: PackageManager.NameNotFoundException) {
|
||||
// couldn't get provider app icon
|
||||
}
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=${e.provider.packageName}"))
|
||||
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
|
||||
if (intent.resolveActivity(pm) != null)
|
||||
notify.setContentIntent(PendingIntent.getActivity(context, 0, intent, flags))
|
||||
|
||||
nm.notifyIfPossible(NotificationUtils.NOTIFY_TASKS_PROVIDER_TOO_OLD, notify.build())
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up sync for the current TaskProvider (and disables sync for unavailable task providers):
|
||||
*
|
||||
* 1. Makes selected tasks authority _syncable_ in the sync framework, all other authorities _not syncable_.
|
||||
* 2. Creates periodic sync worker for selected authority, disables periodic sync workers for all other authorities.
|
||||
* 3. If the permissions don't allow synchronizing with the selected tasks app, a notification is shown.
|
||||
*
|
||||
* Called
|
||||
*
|
||||
* - when a user explicitly selects another task app, or
|
||||
* - when there previously was no (usable) tasks app and [at.bitfire.davdroid.TasksWatcher] detected a new one.
|
||||
*/
|
||||
fun selectProvider(context: Context, selectedProvider: ProviderName?) {
|
||||
Logger.log.info("Selecting tasks app: $selectedProvider")
|
||||
|
||||
val settingsManager = EntryPointAccessors.fromApplication(context, TaskUtilsEntryPoint::class.java).settingsManager()
|
||||
settingsManager.putString(Settings.SELECTED_TASKS_PROVIDER, selectedProvider?.authority)
|
||||
|
||||
var permissionsRequired = false // whether additional permissions are required
|
||||
|
||||
// check all accounts and (de)activate task provider(s) if a CalDAV service is defined
|
||||
val db = EntryPointAccessors.fromApplication(context, SyncUtils.SyncUtilsEntryPoint::class.java).appDatabase()
|
||||
val accountManager = AccountManager.get(context)
|
||||
for (account in accountManager.getAccountsByType(context.getString(R.string.account_type))) {
|
||||
val hasCalDAV = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) != null
|
||||
for (providerName in TaskProvider.ProviderName.entries) {
|
||||
val syncable = hasCalDAV && providerName == selectedProvider
|
||||
|
||||
// enable/disable sync for the given account and authority
|
||||
setSyncable(
|
||||
context,
|
||||
account,
|
||||
providerName.authority,
|
||||
syncable
|
||||
)
|
||||
|
||||
// if sync has just been enabled: check whether additional permissions are required
|
||||
if (syncable && !PermissionUtils.havePermissions(context, providerName.permissions))
|
||||
permissionsRequired = true
|
||||
}
|
||||
}
|
||||
|
||||
if (permissionsRequired) {
|
||||
Logger.log.warning("Tasks synchronization is now enabled for at least one account, but permissions are not granted")
|
||||
PermissionUtils.notifyPermissions(context, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSyncable(context: Context, account: Account, authority: String, syncable: Boolean) {
|
||||
val settingsManager by lazy { EntryPointAccessors.fromApplication(context, SyncUtils.SyncUtilsEntryPoint::class.java).settingsManager() }
|
||||
try {
|
||||
val settings = AccountSettings(context, account)
|
||||
if (syncable) {
|
||||
Logger.log.info("Enabling $authority sync for $account")
|
||||
|
||||
// make account syncable by sync framework
|
||||
ContentResolver.setIsSyncable(account, authority, 1)
|
||||
|
||||
// set sync interval according to settings; also updates periodic sync workers and sync framework on-content-change
|
||||
val interval = settings.getTasksSyncInterval() ?: settingsManager.getLong(Settings.DEFAULT_SYNC_INTERVAL)
|
||||
settings.setSyncInterval(authority, interval)
|
||||
} else {
|
||||
Logger.log.info("Disabling $authority sync for $account")
|
||||
|
||||
// make account not syncable by sync framework
|
||||
ContentResolver.setIsSyncable(account, authority, 0)
|
||||
|
||||
// disable periodic sync worker
|
||||
PeriodicSyncWorker.disable(context, account, authority)
|
||||
}
|
||||
} catch (e: InvalidAccountException) {
|
||||
// account has already been removed, make sure periodic sync is disabled, too
|
||||
PeriodicSyncWorker.disable(context, account, authority)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue