diff options
author | Ricki Hirner <hirner@bitfire.at> | 2022-05-11 12:25:42 +0300 |
---|---|---|
committer | Ricki Hirner <hirner@bitfire.at> | 2022-05-12 11:46:48 +0300 |
commit | 8ca3d857d317e609b759ddf75c3161fa7e6def29 (patch) | |
tree | 780804c11ec4e45214aea1fc5e33e27339175feb | |
parent | 12cc2cfcdec6bf232887af7f2e17c2071b258bb7 (diff) |
Use Koin Dependency Injection for Singletons (#84)
61 files changed, 399 insertions, 395 deletions
diff --git a/app/build.gradle b/app/build.gradle index 60e23d26..9ba305a2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -138,6 +138,7 @@ dependencies { implementation 'commons-io:commons-io:2.6' //noinspection GradleDependency - dnsjava 3+ needs Java 8/Android 7 implementation 'dnsjava:dnsjava:2.1.9' + implementation "io.insert-koin:koin-android:3.2.0" //noinspection GradleDependency implementation "org.apache.commons:commons-collections4:${versions.commonsCollections}" //noinspection GradleDependency @@ -151,9 +152,9 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0' - androidTestImplementation 'junit:junit:4.13.2' androidTestImplementation "com.squareup.okhttp3:mockwebserver:${versions.okhttp}" + androidTestImplementation 'junit:junit:4.13.2' - testImplementation 'junit:junit:4.13.2' testImplementation "com.squareup.okhttp3:mockwebserver:${versions.okhttp}" + testImplementation 'junit:junit:4.13.2' } diff --git a/app/src/androidTest/java/at/bitfire/davdroid/settings/SettingsManagerTest.kt b/app/src/androidTest/java/at/bitfire/davdroid/settings/SettingsManagerTest.kt index 1fd3171c..951d28fe 100644 --- a/app/src/androidTest/java/at/bitfire/davdroid/settings/SettingsManagerTest.kt +++ b/app/src/androidTest/java/at/bitfire/davdroid/settings/SettingsManagerTest.kt @@ -4,14 +4,15 @@ package at.bitfire.davdroid.settings -import androidx.test.platform.app.InstrumentationRegistry import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Test +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject -class SettingsManagerTest { +class SettingsManagerTest: KoinComponent { - val settingsManager by lazy { SettingsManager.getInstance(InstrumentationRegistry.getInstrumentation().targetContext) } + val settingsManager by inject<SettingsManager>() @Test fun testContainsKey_NotExisting() { diff --git a/app/src/main/java/at/bitfire/davdroid/App.kt b/app/src/main/java/at/bitfire/davdroid/App.kt index e4ec204b..b72ffd4b 100644 --- a/app/src/main/java/at/bitfire/davdroid/App.kt +++ b/app/src/main/java/at/bitfire/davdroid/App.kt @@ -10,18 +10,25 @@ import android.net.Uri import android.os.StrictMode import androidx.appcompat.content.res.AppCompatResources import androidx.core.graphics.drawable.toBitmap +import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.AccountSettings -import at.bitfire.davdroid.syncadapter.AccountUtils +import at.bitfire.davdroid.settings.SettingsManager +import at.bitfire.davdroid.syncadapter.AccountsUpdatedListener import at.bitfire.davdroid.ui.DebugInfoActivity import at.bitfire.davdroid.ui.NotificationUtils import at.bitfire.davdroid.ui.UiUtils +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.core.component.KoinComponent +import org.koin.core.component.get +import org.koin.core.context.startKoin import java.util.logging.Level import kotlin.concurrent.thread import kotlin.system.exitProcess @Suppress("unused") -class App: Application(), Thread.UncaughtExceptionHandler { +class App: Application(), KoinComponent, Thread.UncaughtExceptionHandler { companion object { @@ -38,6 +45,27 @@ class App: Application(), Thread.UncaughtExceptionHandler { } + override fun attachBaseContext(base: Context) { + super.attachBaseContext(base) + + startKoin { + androidLogger( + if (BuildConfig.DEBUG) + org.koin.core.logger.Level.DEBUG + else + org.koin.core.logger.Level.INFO + ) + androidContext(base) + + modules( + AccountsUpdatedListener.defaultModule, + AppDatabase.defaultModule, + SettingsManager.defaultModule, + StorageLowReceiver.defaultModule + ) + } + } + override fun onCreate() { super.onCreate() Logger.initialize(this) @@ -59,19 +87,19 @@ class App: Application(), Thread.UncaughtExceptionHandler { NotificationUtils.createChannels(this) // set light/dark mode - UiUtils.setTheme(this) // when this is called in the asynchronous thread below, it recreates - // some current activity and causes an IllegalStateException in rare cases + UiUtils.setTheme() // when this is called in the asynchronous thread below, it recreates + // some current activity and causes an IllegalStateException in rare cases // don't block UI for some background checks thread { // watch for account changes/deletions - AccountUtils.registerAccountsUpdateListener(this) + get<AccountsUpdatedListener>().listen() // foreground service (possible workaround for devices which prevent DAVx5 from being started) ForegroundService.startIfActive(this) // watch storage because low storage means synchronization is stopped - StorageLowReceiver.getInstance(this) + get<StorageLowReceiver>().listen() // watch installed/removed apps TasksWatcher.watch(this) diff --git a/app/src/main/java/at/bitfire/davdroid/Constants.kt b/app/src/main/java/at/bitfire/davdroid/Constants.kt index 3bdbb691..8e6c59bc 100644 --- a/app/src/main/java/at/bitfire/davdroid/Constants.kt +++ b/app/src/main/java/at/bitfire/davdroid/Constants.kt @@ -3,8 +3,6 @@ **************************************************************************************************/ package at.bitfire.davdroid -import java.io.File - object Constants { const val DAVDROID_GREEN_RGBA = 0xFF8bc34a.toInt() diff --git a/app/src/main/java/at/bitfire/davdroid/DavService.kt b/app/src/main/java/at/bitfire/davdroid/DavService.kt index 880185f8..c5d57f2e 100644 --- a/app/src/main/java/at/bitfire/davdroid/DavService.kt +++ b/app/src/main/java/at/bitfire/davdroid/DavService.kt @@ -20,9 +20,9 @@ import at.bitfire.dav4jvm.Response import at.bitfire.dav4jvm.UrlUtils import at.bitfire.dav4jvm.exception.HttpException import at.bitfire.dav4jvm.property.* -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.* import at.bitfire.davdroid.db.Collection +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.Settings import at.bitfire.davdroid.settings.SettingsManager @@ -30,13 +30,15 @@ import at.bitfire.davdroid.ui.DebugInfoActivity import at.bitfire.davdroid.ui.NotificationUtils import okhttp3.HttpUrl import okhttp3.OkHttpClient +import org.koin.android.ext.android.get +import org.koin.core.component.KoinComponent import java.lang.ref.WeakReference import java.util.* import java.util.logging.Level import kotlin.collections.* @Suppress("DEPRECATION") -class DavService: IntentService("DavService") { +class DavService: IntentService("DavService"), KoinComponent { companion object { @@ -93,7 +95,7 @@ class DavService: IntentService("DavService") { listener.get()?.onDavRefreshStatusChanged(id, true) } - val db = AppDatabase.getInstance(this@DavService) + val db = get<AppDatabase>() refreshCollections(db, id) } @@ -160,7 +162,7 @@ class DavService: IntentService("DavService") { } private fun refreshCollections(db: AppDatabase, serviceId: Long) { - val settings = SettingsManager.getInstance(this) + val settings = get<SettingsManager>() val syncAllCollections = settings.getBoolean(Settings.SYNC_ALL_COLLECTIONS) val homeSetDao = db.homeSetDao() diff --git a/app/src/main/java/at/bitfire/davdroid/DavUtils.kt b/app/src/main/java/at/bitfire/davdroid/DavUtils.kt index 55b58467..db9a195b 100644 --- a/app/src/main/java/at/bitfire/davdroid/DavUtils.kt +++ b/app/src/main/java/at/bitfire/davdroid/DavUtils.kt @@ -22,7 +22,6 @@ import okhttp3.MediaType.Companion.toMediaType import org.xbill.DNS.* import java.net.InetAddress import java.util.* -import kotlin.collections.LinkedHashSet /** * Some WebDAV and related network utility methods diff --git a/app/src/main/java/at/bitfire/davdroid/ForegroundService.kt b/app/src/main/java/at/bitfire/davdroid/ForegroundService.kt index 33bb8d85..9a069447 100644 --- a/app/src/main/java/at/bitfire/davdroid/ForegroundService.kt +++ b/app/src/main/java/at/bitfire/davdroid/ForegroundService.kt @@ -16,10 +16,12 @@ import at.bitfire.davdroid.settings.Settings import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.davdroid.ui.AppSettingsActivity import at.bitfire.davdroid.ui.NotificationUtils +import org.koin.core.component.KoinComponent +import org.koin.core.component.get class ForegroundService : Service() { - companion object { + companion object: KoinComponent { /** * Starts/stops a foreground service, according to the app setting [Settings.FOREGROUND_SERVICE] @@ -43,16 +45,15 @@ class ForegroundService : Service() { * Whether the foreground service is enabled (checked) in the app settings. * @return true: foreground service enabled; false: foreground service not enabled */ - fun foregroundServiceActivated(context: Context): Boolean { - val settings = SettingsManager.getInstance(context) - return settings.getBooleanOrNull(Settings.FOREGROUND_SERVICE) == true + fun foregroundServiceActivated(): Boolean { + return get<SettingsManager>().getBooleanOrNull(Settings.FOREGROUND_SERVICE) == true } /** * Starts the foreground service when enabled in the app settings and applicable. */ fun startIfActive(context: Context) { - if (foregroundServiceActivated(context)) { + if (foregroundServiceActivated()) { if (batteryOptimizationWhitelisted(context)) { val serviceIntent = Intent(ACTION_FOREGROUND, null, context, ForegroundService::class.java) if (Build.VERSION.SDK_INT >= 26) @@ -86,7 +87,7 @@ class ForegroundService : Service() { override fun onBind(intent: Intent?): Nothing? = null override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - if (foregroundServiceActivated(this)) { + if (foregroundServiceActivated()) { val settingsIntent = Intent(this, AppSettingsActivity::class.java).apply { putExtra(AppSettingsActivity.EXTRA_SCROLL_TO, Settings.FOREGROUND_SERVICE) } diff --git a/app/src/main/java/at/bitfire/davdroid/HttpClient.kt b/app/src/main/java/at/bitfire/davdroid/HttpClient.kt index 3ef5e638..9038c6c0 100644 --- a/app/src/main/java/at/bitfire/davdroid/HttpClient.kt +++ b/app/src/main/java/at/bitfire/davdroid/HttpClient.kt @@ -10,8 +10,8 @@ import android.security.KeyChain import at.bitfire.cert4android.CustomCertManager import at.bitfire.dav4jvm.BasicDigestAuthHandler import at.bitfire.dav4jvm.UrlUtils -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.Credentials +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.Settings import at.bitfire.davdroid.settings.SettingsManager @@ -19,6 +19,8 @@ import okhttp3.* import okhttp3.brotli.BrotliInterceptor import okhttp3.internal.tls.OkHostnameVerifier import okhttp3.logging.HttpLoggingInterceptor +import org.koin.core.component.KoinComponent +import org.koin.core.component.get import java.io.File import java.net.InetSocketAddress import java.net.Proxy @@ -80,7 +82,7 @@ class HttpClient private constructor( accountSettings: AccountSettings? = null, val logger: java.util.logging.Logger? = Logger.log, val loggerLevel: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.BODY - ) { + ): KoinComponent { private var certManager: CustomCertManager? = null private var certificateAlias: String? = null private var offerCompression: Boolean = false @@ -99,7 +101,7 @@ class HttpClient private constructor( } if (context != null) { - val settings = SettingsManager.getInstance(context) + val settings = get<SettingsManager>() // custom proxy support try { diff --git a/app/src/main/java/at/bitfire/davdroid/MemoryCookieStore.kt b/app/src/main/java/at/bitfire/davdroid/MemoryCookieStore.kt index 648b8ad9..250913d0 100644 --- a/app/src/main/java/at/bitfire/davdroid/MemoryCookieStore.kt +++ b/app/src/main/java/at/bitfire/davdroid/MemoryCookieStore.kt @@ -4,7 +4,6 @@ package at.bitfire.davdroid -import at.bitfire.davdroid.log.Logger import okhttp3.Cookie import okhttp3.CookieJar import okhttp3.HttpUrl diff --git a/app/src/main/java/at/bitfire/davdroid/Singleton.kt b/app/src/main/java/at/bitfire/davdroid/Singleton.kt deleted file mode 100644 index 5d48c9bb..00000000 --- a/app/src/main/java/at/bitfire/davdroid/Singleton.kt +++ /dev/null @@ -1,88 +0,0 @@ -/*************************************************************************************************** - * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. - **************************************************************************************************/ - -package at.bitfire.davdroid - -import android.content.Context -import java.lang.ref.WeakReference - -/** - * A singleton registry that guarantees that there is not more than one instance per class. - * - * It uses weak references so that as soon as the singletons are not used anymore, they can be - * freed by GC. - */ -object Singleton { - - private val currentlyCreating = HashSet<Any>() - - private val singletons = mutableMapOf<Any, WeakReference<Any>>() - - - /** - * Gets a singleton instance of the class, using the Class [T] as key. - * - * This method is thread-safe. - */ - inline fun<reified T> getInstance(noinline createInstance: () -> T): T = - getInstanceByKey(T::class.java, createInstance) - - /** - * Gets a singleton instance of the class, using the Class [T] as key. - * - * Accepts an Android Context, which is used to determine the [Context.getApplicationContext]. - * The application Context is then passed to [createInstance]. - * - * This method is thread-safe. - */ - inline fun<reified T> getInstance(context: Context, noinline createInstance: (appContext: Context) -> T): T = - getInstanceByKey(T::class.java) { - createInstance(context.applicationContext) - } - - - @Synchronized - fun dropAll() { - singletons.clear() - } - - /** - * Gets a singleton instance of the class (using a given key). - * - * This method is thread-safe. - * - * @param key unique key (only one instance is created for this key) - * @param createInstance creates the instance - * - * @throws IllegalStateException when called recursively with the same key - */ - @Synchronized - fun<T> getInstanceByKey(key: Any, createInstance: () -> T): T { - var cached = singletons[key] - if (cached != null && cached.get() == null) { - singletons.remove(cached) - cached = null - } - - // found existing singleton - if (cached != null) - @Suppress("UNCHECKED_CAST") - return cached.get() as T - - // CREATE NEW SINGLETON - // prevent recursive creation - if (currentlyCreating.contains(key)) - throw IllegalStateException("Singleton.getInstance must not be called recursively") - currentlyCreating += key - // actually create the instance - try { - val newInstance = createInstance() - singletons[key] = WeakReference(newInstance) - return newInstance - } finally { - currentlyCreating -= key - } - } - -}
\ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/StorageLowReceiver.kt b/app/src/main/java/at/bitfire/davdroid/StorageLowReceiver.kt index df9a4690..25c79495 100644 --- a/app/src/main/java/at/bitfire/davdroid/StorageLowReceiver.kt +++ b/app/src/main/java/at/bitfire/davdroid/StorageLowReceiver.kt @@ -15,19 +15,24 @@ import androidx.core.app.NotificationManagerCompat import androidx.lifecycle.MutableLiveData import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.ui.NotificationUtils +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module class StorageLowReceiver private constructor( val context: Context ): BroadcastReceiver(), AutoCloseable { companion object { - fun getInstance(context: Context) = - Singleton.getInstance(context) { StorageLowReceiver(context) } + val defaultModule = module { + single { + StorageLowReceiver(androidContext()) + } + } } val storageLow = MutableLiveData<Boolean>(false) - init { + fun listen() { Logger.log.fine("Listening for device storage low/OK broadcasts") val filter = IntentFilter().apply { addAction(Intent.ACTION_DEVICE_STORAGE_LOW) diff --git a/app/src/main/java/at/bitfire/davdroid/TasksWatcher.kt b/app/src/main/java/at/bitfire/davdroid/TasksWatcher.kt index 565ed518..faf8be5e 100644 --- a/app/src/main/java/at/bitfire/davdroid/TasksWatcher.kt +++ b/app/src/main/java/at/bitfire/davdroid/TasksWatcher.kt @@ -10,9 +10,9 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import androidx.annotation.WorkerThread -import at.bitfire.davdroid.log.Logger 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 @@ -21,12 +21,14 @@ import at.bitfire.ical4android.TaskProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.get class TasksWatcher protected constructor( context: Context ): PackageChangedReceiver(context) { - companion object { + companion object: KoinComponent { fun watch(context: Context) = TasksWatcher(context) @@ -42,7 +44,7 @@ class TasksWatcher protected constructor( } // check all accounts and (de)activate task provider(s) if a CalDAV service is defined - val db = AppDatabase.getInstance(context) + val db = get<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 @@ -73,7 +75,7 @@ class TasksWatcher protected constructor( try { val settings = AccountSettings(context, account) val interval = settings.getSavedTasksSyncInterval() ?: - SettingsManager.getInstance(context).getLong(Settings.DEFAULT_SYNC_INTERVAL) + get<SettingsManager>().getLong(Settings.DEFAULT_SYNC_INTERVAL) settings.setSyncInterval(authority, interval) } catch (e: InvalidAccountException) { // account has already been removed diff --git a/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt b/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt index 5de0ab1a..201568b2 100644 --- a/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt +++ b/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt @@ -16,11 +16,13 @@ import androidx.room.* import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import at.bitfire.davdroid.R -import at.bitfire.davdroid.Singleton import at.bitfire.davdroid.TextTable import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.ui.AccountsActivity import at.bitfire.davdroid.ui.NotificationUtils +import org.koin.android.ext.koin.androidContext +import org.koin.core.component.KoinComponent +import org.koin.dsl.module import java.io.Writer @Suppress("ClassName") @@ -45,9 +47,15 @@ abstract class AppDatabase: RoomDatabase() { abstract fun webDavDocumentDao(): WebDavDocumentDao abstract fun webDavMountDao(): WebDavMountDao - companion object { + companion object: KoinComponent { - fun getInstance(context: Context) = Singleton.getInstance<AppDatabase>(context) { + val defaultModule = module { + single { + AppDatabase.createInstance(androidContext()) + } + } + + fun createInstance(context: Context) = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "services.db") .addMigrations(*migrations) .fallbackToDestructiveMigration() // as a last fallback, recreate database instead of crashing @@ -72,7 +80,6 @@ abstract class AppDatabase: RoomDatabase() { } }) .build() - } // migrations diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt index d6101cca..54ab2145 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalAddressBook.kt @@ -16,9 +16,9 @@ import android.provider.ContactsContract.RawContacts import android.util.Base64 import at.bitfire.davdroid.DavUtils import at.bitfire.davdroid.R -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.SyncState +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.syncadapter.AccountUtils import at.bitfire.vcard4android.* diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt index 0af67147..90240734 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalCalendar.kt @@ -13,9 +13,9 @@ import android.provider.CalendarContract.Calendars import android.provider.CalendarContract.Events import at.bitfire.davdroid.Constants import at.bitfire.davdroid.DavUtils -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.SyncState +import at.bitfire.davdroid.log.Logger import at.bitfire.ical4android.AndroidCalendar import at.bitfire.ical4android.AndroidCalendarFactory import at.bitfire.ical4android.BatchOperation diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalJtxICalObject.kt b/app/src/main/java/at/bitfire/davdroid/resource/LocalJtxICalObject.kt index 9c262d13..1627fc57 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalJtxICalObject.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalJtxICalObject.kt @@ -5,7 +5,9 @@ package at.bitfire.davdroid.resource import android.content.ContentValues -import at.bitfire.ical4android.* +import at.bitfire.ical4android.JtxCollection +import at.bitfire.ical4android.JtxICalObject +import at.bitfire.ical4android.JtxICalObjectFactory import at.techbee.jtx.JtxContract class LocalJtxICalObject( diff --git a/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.kt b/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.kt index 6a4c1f7c..20ea115a 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/LocalTaskList.kt @@ -11,9 +11,9 @@ import android.content.Context import android.net.Uri import at.bitfire.davdroid.Constants import at.bitfire.davdroid.DavUtils -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.SyncState +import at.bitfire.davdroid.log.Logger import at.bitfire.ical4android.AndroidTaskList import at.bitfire.ical4android.AndroidTaskListFactory import at.bitfire.ical4android.TaskProvider diff --git a/app/src/main/java/at/bitfire/davdroid/resource/TaskUtils.kt b/app/src/main/java/at/bitfire/davdroid/resource/TaskUtils.kt index 89298720..a0c525ee 100644 --- a/app/src/main/java/at/bitfire/davdroid/resource/TaskUtils.kt +++ b/app/src/main/java/at/bitfire/davdroid/resource/TaskUtils.kt @@ -12,11 +12,15 @@ import at.bitfire.ical4android.TaskProvider.ProviderName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject -object TaskUtils { +object TaskUtils: KoinComponent { + + val settingsManager by inject<SettingsManager>() fun currentProvider(context: Context): ProviderName? { - val preferredAuthority = SettingsManager.getInstance(context).getString(Settings.PREFERRED_TASKS_PROVIDER) + val preferredAuthority = settingsManager.getString(Settings.PREFERRED_TASKS_PROVIDER) ProviderName.values() .sortedByDescending { it.authority == preferredAuthority } .forEach { providerName -> @@ -29,7 +33,7 @@ object TaskUtils { fun isAvailable(context: Context) = currentProvider(context) != null fun setPreferredProvider(context: Context, providerName: ProviderName) { - SettingsManager.getInstance(context).putString(Settings.PREFERRED_TASKS_PROVIDER, providerName.authority) + settingsManager.putString(Settings.PREFERRED_TASKS_PROVIDER, providerName.authority) CoroutineScope(Dispatchers.Default).launch { TasksWatcher.updateTaskSync(context) } diff --git a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt index 4088492c..0cc92a85 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt @@ -25,11 +25,11 @@ import at.bitfire.davdroid.InvalidAccountException import at.bitfire.davdroid.R import at.bitfire.davdroid.TasksWatcher import at.bitfire.davdroid.closeCompat -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.Credentials 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 @@ -46,6 +46,9 @@ import net.fortuna.ical4j.model.property.Url import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.apache.commons.lang3.StringUtils import org.dmfs.tasks.contract.TaskContract +import org.koin.core.component.KoinComponent +import org.koin.core.component.get +import org.koin.core.component.inject import java.io.ByteArrayInputStream import java.io.ObjectInputStream import java.util.logging.Level @@ -59,7 +62,7 @@ import java.util.logging.Level class AccountSettings( val context: Context, val account: Account -) { +): KoinComponent { companion object { @@ -174,10 +177,10 @@ class AccountSettings( } } - - + + val accountManager: AccountManager = AccountManager.get(context) - val settings = SettingsManager.getInstance(context) + val settings by inject<SettingsManager>() init { synchronized(AccountSettings::class.java) { @@ -581,7 +584,7 @@ class AccountSettings( * Disable it on those accounts for the future. */ private fun update_8_9() { - val db = AppDatabase.getInstance(context) + val db = get<AppDatabase>() val hasCalDAV = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) != null if (!hasCalDAV && ContentResolver.getIsSyncable(account, OpenTasks.authority) != 0) { Logger.log.info("Disabling OpenTasks sync for $account") diff --git a/app/src/main/java/at/bitfire/davdroid/settings/SettingsManager.kt b/app/src/main/java/at/bitfire/davdroid/settings/SettingsManager.kt index 7ea85342..80b7e535 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/SettingsManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/SettingsManager.kt @@ -7,8 +7,9 @@ package at.bitfire.davdroid.settings import android.content.Context import android.util.NoSuchPropertyException import androidx.annotation.AnyThread -import at.bitfire.davdroid.Singleton import at.bitfire.davdroid.log.Logger +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module import java.io.Writer import java.lang.ref.WeakReference import java.util.* @@ -23,8 +24,11 @@ class SettingsManager private constructor( ) { companion object { - fun getInstance(context: Context) = - Singleton.getInstance(context) { SettingsManager(context) } + val defaultModule = module { + single { + SettingsManager(androidContext()) + } + } } private val providers = LinkedList<SettingsProvider>() diff --git a/app/src/main/java/at/bitfire/davdroid/settings/SharedPreferencesProvider.kt b/app/src/main/java/at/bitfire/davdroid/settings/SharedPreferencesProvider.kt index 65c43ae0..664a79e4 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/SharedPreferencesProvider.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/SharedPreferencesProvider.kt @@ -9,14 +9,16 @@ import android.content.Context.MODE_PRIVATE import android.content.SharedPreferences import androidx.preference.PreferenceManager import at.bitfire.davdroid.TextTable -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.log.Logger +import org.koin.core.component.KoinComponent +import org.koin.core.component.get import java.io.Writer class SharedPreferencesProvider( val context: Context, val settingsManager: SettingsManager -): SettingsProvider, SharedPreferences.OnSharedPreferenceChangeListener { +): KoinComponent, SettingsProvider, SharedPreferences.OnSharedPreferenceChangeListener { companion object { private const val META_VERSION = "version" @@ -124,7 +126,7 @@ class SharedPreferencesProvider( edit.apply() // open ServiceDB to upgrade it and possibly migrate settings - AppDatabase.getInstance(context) + get<AppDatabase>() } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountUtils.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountUtils.kt index e5960285..add388be 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountUtils.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountUtils.kt @@ -6,19 +6,9 @@ package at.bitfire.davdroid.syncadapter import android.accounts.Account import android.accounts.AccountManager -import android.accounts.OnAccountsUpdateListener import android.content.Context import android.os.Bundle -import androidx.annotation.AnyThread -import at.bitfire.davdroid.R -import at.bitfire.davdroid.Singleton import at.bitfire.davdroid.log.Logger -import at.bitfire.davdroid.db.AppDatabase -import at.bitfire.davdroid.resource.LocalAddressBook -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import java.util.logging.Level object AccountUtils { @@ -60,15 +50,6 @@ object AccountUtils { return true } - fun registerAccountsUpdateListener(context: Context) { - val listener = Singleton.getInstance(context) { - AccountsUpdatedListener(it) - } - - val accountManager = AccountManager.get(context) - accountManager.addOnAccountsUpdatedListener(listener, null, true) - } - private fun verifyUserData(context: Context, account: Account, userData: Bundle): Boolean { val accountManager = AccountManager.get(context) userData.keySet().forEach { key -> @@ -83,56 +64,4 @@ object AccountUtils { } - class AccountsUpdatedListener(val context: Context): OnAccountsUpdateListener { - - /** - * Called when the main accounts have been updated, including when a main account has been - * removed. In the latter case, this method fulfills two tasks: - * - * 1. Remove related address book accounts. - * 2. Remove related service entries from the [AppDatabase]. - */ - @AnyThread - override fun onAccountsUpdated(accounts: Array<out Account>) { - /* onAccountsUpdated may be called from the main thread, but cleanupAccounts - requires disk (database) access. So we launch it in a separate thread. */ - CoroutineScope(Dispatchers.Default).launch { - cleanupAccounts(context, accounts) - } - } - - @Synchronized - private fun cleanupAccounts(context: Context, accounts: Array<out Account>) { - Logger.log.log(Level.INFO, "Cleaning up accounts. Current accounts:", accounts) - - val mainAccountType = context.getString(R.string.account_type) - val mainAccountNames = accounts - .filter { account -> account.type == mainAccountType } - .map { it.name } - - val addressBookAccountType = context.getString(R.string.account_type_address_book) - val addressBooks = accounts - .filter { account -> account.type == addressBookAccountType } - .map { addressBookAccount -> LocalAddressBook(context, addressBookAccount, null) } - for (addressBook in addressBooks) { - try { - if (!mainAccountNames.contains(addressBook.mainAccount.name)) - // the main account for this address book doesn't exist anymore - addressBook.delete() - } catch(e: Exception) { - Logger.log.log(Level.SEVERE, "Couldn't delete address book account", e) - } - } - - // delete orphaned services in DB - val db = AppDatabase.getInstance(context) - val serviceDao = db.serviceDao() - if (mainAccountNames.isEmpty()) - serviceDao.deleteAll() - else - serviceDao.deleteExceptAccounts(mainAccountNames.toTypedArray()) - } - - } - }
\ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountsUpdatedListener.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountsUpdatedListener.kt new file mode 100644 index 00000000..654488cb --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/AccountsUpdatedListener.kt @@ -0,0 +1,88 @@ +/*************************************************************************************************** + * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. + **************************************************************************************************/ + +package at.bitfire.davdroid.syncadapter + +import android.accounts.Account +import android.accounts.AccountManager +import android.accounts.OnAccountsUpdateListener +import android.content.Context +import androidx.annotation.AnyThread +import at.bitfire.davdroid.R +import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.log.Logger +import at.bitfire.davdroid.resource.LocalAddressBook +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.koin.android.ext.koin.androidContext +import org.koin.core.component.KoinComponent +import org.koin.core.component.get +import org.koin.dsl.module +import java.util.logging.Level + +class AccountsUpdatedListener(val context: Context): KoinComponent, OnAccountsUpdateListener { + + companion object { + val defaultModule = module { + single { + AccountsUpdatedListener(androidContext()) + } + } + } + + + fun listen() { + val accountManager = AccountManager.get(context) + accountManager.addOnAccountsUpdatedListener(this, null, true) + } + + /** + * Called when the main accounts have been updated, including when a main account has been + * removed. In the latter case, this method fulfills two tasks: + * + * 1. Remove related address book accounts. + * 2. Remove related service entries from the [AppDatabase]. + */ + @AnyThread + override fun onAccountsUpdated(accounts: Array<out Account>) { + /* onAccountsUpdated may be called from the main thread, but cleanupAccounts + requires disk (database) access. So we launch it in a separate thread. */ + CoroutineScope(Dispatchers.Default).launch { + cleanupAccounts(context, accounts) + } + } + + @Synchronized + private fun cleanupAccounts(context: Context, accounts: Array<out Account>) { + Logger.log.log(Level.INFO, "Cleaning up accounts. Current accounts:", accounts) + + val mainAccountType = context.getString(R.string.account_type) + val mainAccountNames = accounts + .filter { account -> account.type == mainAccountType } + .map { it.name } + + val addressBookAccountType = context.getString(R.string.account_type_address_book) + val addressBooks = accounts + .filter { account -> account.type == addressBookAccountType } + .map { addressBookAccount -> LocalAddressBook(context, addressBookAccount, null) } + for (addressBook in addressBooks) { + try { + if (!mainAccountNames.contains(addressBook.mainAccount.name)) + // the main account for this address book doesn't exist anymore + addressBook.delete() + } catch(e: Exception) { + Logger.log.log(Level.SEVERE, "Couldn't delete address book account", e) + } + } + + // delete orphaned services in DB + val serviceDao = get<AppDatabase>().serviceDao() + if (mainAccountNames.isEmpty()) + serviceDao.deleteAll() + else + serviceDao.deleteExceptAccounts(mainAccountNames.toTypedArray()) + } + +}
\ No newline at end of file diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/AddressBooksSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/AddressBooksSyncAdapterService.kt index 27880329..c52bfd4b 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/AddressBooksSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/AddressBooksSyncAdapterService.kt @@ -14,14 +14,15 @@ import android.os.Bundle import android.provider.ContactsContract import androidx.core.content.ContextCompat import at.bitfire.davdroid.closeCompat -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.Service +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalAddressBook import at.bitfire.davdroid.settings.AccountSettings import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl +import org.koin.core.component.get import java.util.logging.Level class AddressBooksSyncAdapterService : SyncAdapterService() { @@ -58,7 +59,7 @@ class AddressBooksSyncAdapterService : SyncAdapterService() { } private fun updateLocalAddressBooks(account: Account, syncResult: SyncResult): Boolean { - val db = AppDatabase.getInstance(context) + val db = get<AppDatabase>() val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CARDDAV) val remoteAddressBooks = mutableMapOf<HttpUrl, Collection>() diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt index 56ff7b99..2e2d2588 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt @@ -15,8 +15,8 @@ import at.bitfire.dav4jvm.exception.DavException import at.bitfire.dav4jvm.property.* import at.bitfire.davdroid.DavUtils import at.bitfire.davdroid.R -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.SyncState +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalCalendar import at.bitfire.davdroid.resource.LocalEvent import at.bitfire.davdroid.resource.LocalResource diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt index 331c6f1e..4ac22118 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.kt @@ -10,15 +10,16 @@ import android.content.Context import android.content.SyncResult import android.os.Bundle import android.provider.CalendarContract -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.Service +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalCalendar import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.ical4android.AndroidCalendar import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl +import org.koin.core.component.get import java.util.logging.Level class CalendarsSyncAdapterService: SyncAdapterService() { @@ -63,7 +64,7 @@ class CalendarsSyncAdapterService: SyncAdapterService() { } private fun updateLocalCalendars(provider: ContentProviderClient, account: Account, settings: AccountSettings) { - val db = AppDatabase.getInstance(context) + val db = get<AppDatabase>() val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) val remoteCalendars = mutableMapOf<HttpUrl, Collection>() diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt index 2603bc11..3f6e940a 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt @@ -20,8 +20,8 @@ import at.bitfire.davdroid.DavUtils import at.bitfire.davdroid.DavUtils.sameTypeAs import at.bitfire.davdroid.HttpClient import at.bitfire.davdroid.R -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.SyncState +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.* import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.syncadapter.groups.CategoriesStrategy diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/JtxSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/JtxSyncAdapterService.kt index 60264fd1..53ee9a3c 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/JtxSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/JtxSyncAdapterService.kt @@ -12,16 +12,17 @@ import android.content.Context import android.content.SyncResult import android.os.Build import android.os.Bundle -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.Service +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalJtxCollection import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.ical4android.JtxCollection import at.bitfire.ical4android.TaskProvider import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl +import org.koin.core.component.get import java.util.logging.Level class JtxSyncAdapterService: SyncAdapterService() { @@ -69,7 +70,7 @@ class JtxSyncAdapterService: SyncAdapterService() { } private fun updateLocalCollections(account: Account, client: ContentProviderClient) { - val db = AppDatabase.getInstance(context) + val db = get<AppDatabase>() val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) val remoteCollections = mutableMapOf<HttpUrl, Collection>() diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/JtxSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/JtxSyncManager.kt index 00769178..638c36f5 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/JtxSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/JtxSyncManager.kt @@ -15,8 +15,8 @@ import at.bitfire.dav4jvm.exception.DavException import at.bitfire.dav4jvm.property.* import at.bitfire.davdroid.DavUtils import at.bitfire.davdroid.R -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.SyncState +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalJtxCollection import at.bitfire.davdroid.resource.LocalJtxICalObject import at.bitfire.davdroid.resource.LocalResource diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAdapterService.kt index 1b293202..17877a20 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncAdapterService.kt @@ -18,6 +18,7 @@ import at.bitfire.davdroid.PermissionUtils import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.ui.account.WifiPermissionsActivity +import org.koin.core.component.KoinComponent import java.util.* import java.util.logging.Level @@ -77,7 +78,7 @@ abstract class SyncAdapterService: Service() { context, true // isSyncable shouldn't be -1 because DAVx5 sets it to 0 or 1. // However, if it is -1 by accident, set it to 1 to avoid endless sync loops. - ) { + ), KoinComponent { companion object { diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt index 374377d8..5be1c897 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/SyncManager.kt @@ -24,10 +24,10 @@ import at.bitfire.dav4jvm.property.GetETag import at.bitfire.dav4jvm.property.ScheduleTag import at.bitfire.dav4jvm.property.SyncToken import at.bitfire.davdroid.* -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.SyncState import at.bitfire.davdroid.db.SyncStats +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.* import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.ui.DebugInfoActivity @@ -38,17 +38,17 @@ import at.bitfire.ical4android.Ical4Android import at.bitfire.ical4android.TaskProvider import at.bitfire.ical4android.UsesThreadContextClassLoader import at.bitfire.vcard4android.ContactsStorageException -import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext +import kotlinx.coroutines.* import okhttp3.HttpUrl import okhttp3.RequestBody import org.apache.commons.io.FileUtils import org.apache.commons.lang3.exception.ContextedException import org.dmfs.tasks.contract.TaskContract +import org.koin.core.component.KoinComponent +import org.koin.core.component.get import java.io.IOException import java.io.InterruptedIOException +import java.lang.ref.WeakReference import java.net.HttpURLConnection import java.security.cert.CertificateException import java.util.* @@ -68,7 +68,7 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L val authority: String, val syncResult: SyncResult, val localCollection: CollectionType -): AutoCloseable { +): AutoCloseable, KoinComponent { enum class SyncAlgorithm { PROPFIND_REPORT, @@ -78,6 +78,27 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L companion object { const val DEBUG_INFO_MAX_RESOURCE_DUMP_SIZE = 100*FileUtils.ONE_KB.toInt() const val MAX_MULTIGET_RESOURCES = 10 + + var _workDispatcher: WeakReference<CoroutineDispatcher>? = null + /** + * We use our own dispatcher to + * + * - make sure that all threads have [Thread.getContextClassLoader] set, which is required for dav4jvm and ical4j (because they rely on [ServiceLoader]), + * - control the global number of sync worker threads. + * + * Threads created by a service automatically have a contextClassLoader. + */ + fun getWorkDispatcher(): CoroutineDispatcher { + val cached = _workDispatcher?.get() + if (cached != null) + return cached + + val newDispatcher = ThreadPoolExecutor( + 0, Integer.min(Runtime.getRuntime().availableProcessors(), 4), + 10, TimeUnit.SECONDS, LinkedBlockingQueue() + ).asCoroutineDispatcher() + return newDispatcher + } } init { @@ -100,20 +121,7 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L protected var hasCollectionSync = false - /** - * We use our own dispatcher to - * - * - make sure that all threads have [Thread.getContextClassLoader] set, which is required for dav4jvm and ical4j (because they rely on [ServiceLoader]), - * - control the global number of sync worker threads. - * - * Threads created by a service automatically have a contextClassLoader. - */ - val workDispatcher = Singleton.getInstanceByKey("SyncManager.workDispatcher") { - ThreadPoolExecutor( - 0, Integer.min(Runtime.getRuntime().availableProcessors(), 4), - 10, TimeUnit.SECONDS, LinkedBlockingQueue() - ).asCoroutineDispatcher() - } + val workDispatcher = getWorkDispatcher() override fun close() { @@ -133,7 +141,7 @@ abstract class SyncManager<ResourceType: LocalResource<*>, out CollectionType: L } // log sync time - val db = AppDatabase.getInstance(context) + val db = get<AppDatabase>() db.runInTransaction { db.collectionDao().getByUrl(collectionURL.toString())?.let { collection -> db.syncStatsDao().insertOrReplace( diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.kt index a7ad922b..8e33ec33 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncAdapterService.kt @@ -11,10 +11,10 @@ import android.content.Context import android.content.SyncResult import android.os.Build import android.os.Bundle -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.Service +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalTaskList import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.ical4android.AndroidTaskList @@ -22,6 +22,7 @@ import at.bitfire.ical4android.TaskProvider import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import org.dmfs.tasks.contract.TaskContract +import org.koin.core.component.get import java.util.logging.Level /** @@ -75,7 +76,7 @@ open class TasksSyncAdapterService: SyncAdapterService() { } private fun updateLocalTaskLists(provider: TaskProvider, account: Account, settings: AccountSettings) { - val db = AppDatabase.getInstance(context) + val db = get<AppDatabase>() val service = db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV) val remoteTaskLists = mutableMapOf<HttpUrl, Collection>() diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt index 312e715e..3987c498 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt @@ -15,8 +15,8 @@ import at.bitfire.dav4jvm.exception.DavException import at.bitfire.dav4jvm.property.* import at.bitfire.davdroid.DavUtils import at.bitfire.davdroid.R -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.SyncState +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalResource import at.bitfire.davdroid.resource.LocalTask import at.bitfire.davdroid.resource.LocalTaskList diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.kt index 9722c7e2..64f04a25 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/AboutActivity.kt @@ -69,7 +69,7 @@ class AboutActivity: AppCompatActivity() { binding.tabs.setupWithViewPager(binding.viewpager, false) } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { + override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.activity_about, menu) return true } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AccountListFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/AccountListFragment.kt index 980e7596..e34a08ac 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AccountListFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/AccountListFragment.kt @@ -34,9 +34,11 @@ import at.bitfire.davdroid.StorageLowReceiver import at.bitfire.davdroid.databinding.AccountListBinding import at.bitfire.davdroid.databinding.AccountListItemBinding import at.bitfire.davdroid.ui.account.AccountActivity +import org.koin.core.component.KoinComponent +import org.koin.core.component.get import java.text.Collator -class AccountListFragment: Fragment() { +class AccountListFragment: Fragment(), KoinComponent { private var _binding: AccountListBinding? = null private val binding get() = _binding!! @@ -61,7 +63,7 @@ class AccountListFragment: Fragment() { startActivity(intent) } - StorageLowReceiver.getInstance(requireActivity()).storageLow.observe(viewLifecycleOwner) { storageLow -> + get<StorageLowReceiver>().storageLow.observe(viewLifecycleOwner) { storageLow -> binding.lowStorageInfo.visibility = if (storageLow) View.VISIBLE else View.GONE } binding.manageStorage.setOnClickListener { diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.kt index cc1719ff..062aa98c 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.kt @@ -29,6 +29,8 @@ import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.net.URI import java.net.URISyntaxException import kotlin.math.roundToInt @@ -53,9 +55,9 @@ class AppSettingsActivity: AppCompatActivity() { } - class SettingsFragment: PreferenceFragmentCompat(), SettingsManager.OnChangeListener { + class SettingsFragment: PreferenceFragmentCompat(), KoinComponent, SettingsManager.OnChangeListener { - val settings by lazy { SettingsManager.getInstance(requireActivity()) } + val settings by inject<SettingsManager>() val onBatteryOptimizationResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { loadSettings() @@ -256,7 +258,6 @@ class AppSettingsActivity: AppCompatActivity() { } private fun resetHints() { - val settings = SettingsManager.getInstance(requireActivity()) settings.remove(BatteryOptimizationsFragment.Model.HINT_BATTERY_OPTIMIZATIONS) settings.remove(BatteryOptimizationsFragment.Model.HINT_AUTOSTART_PERMISSION) settings.remove(TasksFragment.Model.HINT_OPENTASKS_NOT_INSTALLED) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/BaseAccountsDrawerHandler.kt b/app/src/main/java/at/bitfire/davdroid/ui/BaseAccountsDrawerHandler.kt index 5fd868b4..7e4e5252 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/BaseAccountsDrawerHandler.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/BaseAccountsDrawerHandler.kt @@ -8,16 +8,12 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri -import android.util.Log import android.view.Menu import android.view.MenuItem import android.widget.Toast import androidx.annotation.CallSuper -import at.bitfire.davdroid.App import at.bitfire.davdroid.BuildConfig import at.bitfire.davdroid.R -import at.bitfire.davdroid.log.Logger -import at.bitfire.davdroid.ui.webdav.WebdavMountsActivity /** * Default menu items control diff --git a/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt index 753e7d70..0b1d249d 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/DebugInfoActivity.kt @@ -39,8 +39,8 @@ import at.bitfire.davdroid.InvalidAccountException import at.bitfire.davdroid.R import at.bitfire.davdroid.TextTable import at.bitfire.davdroid.databinding.ActivityDebugInfoBinding -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalAddressBook import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.SettingsManager @@ -57,6 +57,8 @@ import org.apache.commons.io.IOUtils import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.exception.ExceptionUtils import org.dmfs.tasks.contract.TaskContract +import org.koin.core.component.KoinComponent +import org.koin.core.component.get import java.io.* import java.util.* import java.util.logging.Level @@ -171,7 +173,7 @@ class DebugInfoActivity: AppCompatActivity() { class ReportModel( val context: Application - ): AndroidViewModel(context) { + ): AndroidViewModel(context), KoinComponent { private var initialized = false @@ -469,11 +471,11 @@ class DebugInfoActivity: AppCompatActivity() { // database dump writer.append("\nDATABASE DUMP\n\n") - AppDatabase.getInstance(context).dump(writer, arrayOf("webdav_document")) + get<AppDatabase>().dump(writer, arrayOf("webdav_document")) // app settings writer.append("\nAPP SETTINGS\n\n") - SettingsManager.getInstance(context).dump(writer) + get<SettingsManager>().dump(writer) writer.append("--- END DEBUG INFO ---\n") writer.toString() diff --git a/app/src/main/java/at/bitfire/davdroid/ui/TasksFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/TasksFragment.kt index ce37b833..2f043f64 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/TasksFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/TasksFragment.kt @@ -26,6 +26,8 @@ import at.bitfire.davdroid.resource.TaskUtils import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.ical4android.TaskProvider.ProviderName import com.google.android.material.snackbar.Snackbar +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject class TasksFragment: Fragment() { @@ -94,7 +96,7 @@ class TasksFragment: Fragment() { } - class Model(app: Application) : AndroidViewModel(app), SettingsManager.OnChangeListener { + class Model(app: Application) : AndroidViewModel(app), KoinComponent, SettingsManager.OnChangeListener { companion object { @@ -107,7 +109,7 @@ class TasksFragment: Fragment() { } - val settings = SettingsManager.getInstance(app) + val settings by inject<SettingsManager>() val currentProvider = MutableLiveData<ProviderName>() val openTasksInstalled = MutableLiveData<Boolean>() @@ -126,7 +128,6 @@ class TasksFragment: Fragment() { } val dontShow = object: ObservableBoolean() { - val settings = SettingsManager.getInstance(getApplication()) override fun get() = settings.getBooleanOrNull(HINT_OPENTASKS_NOT_INSTALLED) == false override fun set(dontShowAgain: Boolean) { if (dontShowAgain) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/UiUtils.kt b/app/src/main/java/at/bitfire/davdroid/ui/UiUtils.kt index 9f207add..d7aeabd8 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/UiUtils.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/UiUtils.kt @@ -19,9 +19,11 @@ import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.Settings import at.bitfire.davdroid.settings.SettingsManager +import org.koin.core.component.KoinComponent +import org.koin.core.component.get import java.util.logging.Level -object UiUtils { +object UiUtils: KoinComponent { const val SHORTCUT_SYNC_ALL = "syncAllAccounts" const val SNACKBAR_LENGTH_VERY_LONG = 5000 // 5s @@ -52,8 +54,8 @@ object UiUtils { return false } - fun setTheme(context: Context) { - val settings = SettingsManager.getInstance(context) + fun setTheme() { + val settings = get<SettingsManager>() val mode = settings.getIntOrNull(Settings.PREFERRED_THEME) ?: Settings.PREFERRED_THEME_DEFAULT AppCompatDelegate.setDefaultNightMode(mode) } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt index 2fb2d88a..59091f34 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/AccountActivity.kt @@ -23,16 +23,18 @@ import androidx.lifecycle.* import at.bitfire.davdroid.DavUtils import at.bitfire.davdroid.R import at.bitfire.davdroid.databinding.ActivityAccountBinding -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.db.Service +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.AccountSettings import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.util.logging.Level class AccountActivity: AppCompatActivity() { @@ -80,7 +82,7 @@ class AccountActivity: AppCompatActivity() { } } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { + override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.activity_account, menu) return true } @@ -231,7 +233,7 @@ class AccountActivity: AppCompatActivity() { class Model( application: Application, val account: Account - ): AndroidViewModel(application), OnAccountsUpdateListener { + ): AndroidViewModel(application), KoinComponent, OnAccountsUpdateListener { class Factory( val application: Application, @@ -242,7 +244,7 @@ class AccountActivity: AppCompatActivity() { Model(application, account) as T } - private val db = AppDatabase.getInstance(application) + private val db by inject<AppDatabase>() val accountManager = AccountManager.get(application)!! val accountSettings by lazy { AccountSettings(getApplication(), account) } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionInfoFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionInfoFragment.kt index 31dfde73..96016f2d 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionInfoFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionInfoFragment.kt @@ -20,6 +20,8 @@ import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Collection import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject class CollectionInfoFragment: DialogFragment() { @@ -54,20 +56,21 @@ class CollectionInfoFragment: DialogFragment() { class Model( application: Application - ): AndroidViewModel(application) { + ): AndroidViewModel(application), KoinComponent { + val db by inject<AppDatabase>() var collection = MutableLiveData<Collection>() private var initialized = false @UiThread fun initialize(collectionId: Long) { + // TODO use constructor and model factory instead of custom initialize() if (initialized) return initialized = true viewModelScope.launch(Dispatchers.IO) { - val db = AppDatabase.getInstance(getApplication()) collection.postValue(db.collectionDao().get(collectionId)) } } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionsFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionsFragment.kt index 28aa8bb5..7c4787d7 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionsFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/CollectionsFragment.kt @@ -32,14 +32,15 @@ import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Collection import at.bitfire.davdroid.resource.LocalAddressBook import at.bitfire.davdroid.resource.TaskUtils -import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.davdroid.ui.PermissionsActivity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject -abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshListener { +abstract class CollectionsFragment: Fragment(), KoinComponent, SwipeRefreshLayout.OnRefreshListener { companion object { const val EXTRA_SERVICE_ID = "serviceId" @@ -146,8 +147,6 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList } override fun onPrepareOptionsMenu(menu: Menu) { - val settings = SettingsManager.getInstance(requireActivity()) - menu.findItem(R.id.showOnlyPersonal).let { showOnlyPersonal -> accountModel.showOnlyPersonal.value?.let { value -> showOnlyPersonal.isChecked = value @@ -266,9 +265,9 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList class Model( val context: Application, val accountModel: AccountActivity.Model - ): ViewModel(), DavService.RefreshingStatusListener, SyncStatusObserver { + ): ViewModel(), DavService.RefreshingStatusListener, KoinComponent, SyncStatusObserver { - private val db = AppDatabase.getInstance(context) + private val db by inject<AppDatabase>() val serviceId = MutableLiveData<Long>() private lateinit var collectionType: String diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/CreateAddressBookActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/CreateAddressBookActivity.kt index c18ee7fa..a3fdb297 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/CreateAddressBookActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/CreateAddressBookActivity.kt @@ -29,6 +29,8 @@ import at.bitfire.davdroid.ui.HomeSetAdapter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.apache.commons.lang3.StringUtils +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.util.* class CreateAddressBookActivity: AppCompatActivity() { @@ -122,9 +124,10 @@ class CreateAddressBookActivity: AppCompatActivity() { class Model( application: Application - ) : AndroidViewModel(application) { + ) : AndroidViewModel(application), KoinComponent { var account: Account? = null + val db by inject<AppDatabase>() val displayName = MutableLiveData<String>() val displayNameError = MutableLiveData<String>() @@ -140,13 +143,13 @@ class CreateAddressBookActivity: AppCompatActivity() { @MainThread fun initialize(account: Account) { + // TODO use constructor and model factory instead of custom initialize() if (this.account != null) return this.account = account viewModelScope.launch(Dispatchers.IO) { // load account info - val db = AppDatabase.getInstance(getApplication()) db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CARDDAV)?.let { service -> homeSets.postValue(db.homeSetDao().getBindableByService(service.id)) } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/CreateCalendarActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/CreateCalendarActivity.kt index 4384cf69..a64c2e96 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/CreateCalendarActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/CreateCalendarActivity.kt @@ -36,6 +36,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import net.fortuna.ical4j.model.Calendar import org.apache.commons.lang3.StringUtils +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.time.ZoneId import java.time.format.TextStyle import java.util.* @@ -210,9 +212,10 @@ class CreateCalendarActivity: AppCompatActivity(), ColorPickerDialogListener { class Model( application: Application - ): AndroidViewModel(application) { + ): AndroidViewModel(application), KoinComponent { var account: Account? = null + val db by inject<AppDatabase>() val displayName = MutableLiveData<String>() val displayNameError = MutableLiveData<String>() @@ -244,7 +247,6 @@ class CreateCalendarActivity: AppCompatActivity(), ColorPickerDialogListener { viewModelScope.launch(Dispatchers.IO) { // load account info - val db = AppDatabase.getInstance(getApplication()) db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CALDAV)?.let { service -> homeSets.postValue(db.homeSetDao().getBindableByService(service.id)) } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/CreateCollectionFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/CreateCollectionFragment.kt index 5a8cbd13..cca37bd1 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/CreateCollectionFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/CreateCollectionFragment.kt @@ -19,15 +19,17 @@ import at.bitfire.davdroid.DavService import at.bitfire.davdroid.DavUtils import at.bitfire.davdroid.HttpClient import at.bitfire.davdroid.R -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Collection +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.ui.ExceptionInfoFragment import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch import okhttp3.HttpUrl.Companion.toHttpUrl +import org.koin.core.component.KoinComponent +import org.koin.core.component.get import java.io.IOException import java.io.StringWriter import java.util.logging.Level @@ -103,7 +105,7 @@ class CreateCollectionFragment: DialogFragment() { class Model( app: Application - ): AndroidViewModel(app) { + ): AndroidViewModel(app), KoinComponent { lateinit var account: Account lateinit var serviceType: String @@ -123,7 +125,7 @@ class CreateCollectionFragment: DialogFragment() { dav.mkCol(generateXml()) {} // no HTTP error -> create collection locally - val db = AppDatabase.getInstance(getApplication()) + val db = get<AppDatabase>() db.serviceDao().getByAccountAndType(account.name, serviceType)?.let { service -> collection.serviceId = service.id db.collectionDao().insert(collection) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/DeleteCollectionFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/DeleteCollectionFragment.kt index da6c5a89..081d0a8c 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/DeleteCollectionFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/DeleteCollectionFragment.kt @@ -24,6 +24,8 @@ import at.bitfire.davdroid.ui.ExceptionInfoFragment import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject class DeleteCollectionFragment: DialogFragment() { @@ -81,12 +83,12 @@ class DeleteCollectionFragment: DialogFragment() { class Model( application: Application - ): AndroidViewModel(application) { + ): AndroidViewModel(application), KoinComponent { var account: Account? = null var collectionInfo: Collection? = null - val db = AppDatabase.getInstance(application) + val db by inject<AppDatabase>() val confirmation = MutableLiveData<Boolean>() val result = MutableLiveData<Exception>() diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/RenameAccountFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/RenameAccountFragment.kt index e7d9b624..4ca95d85 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/RenameAccountFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/RenameAccountFragment.kt @@ -30,8 +30,8 @@ import at.bitfire.davdroid.DavUtils import at.bitfire.davdroid.InvalidAccountException import at.bitfire.davdroid.R import at.bitfire.davdroid.closeCompat -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.LocalAddressBook import at.bitfire.davdroid.resource.LocalTaskList import at.bitfire.davdroid.settings.AccountSettings @@ -40,6 +40,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.get import java.util.logging.Level class RenameAccountFragment: DialogFragment() { @@ -98,7 +100,7 @@ class RenameAccountFragment: DialogFragment() { class Model( application: Application - ): AndroidViewModel(application) { + ): AndroidViewModel(application), KoinComponent { val finished = MutableLiveData<Boolean>() @@ -148,7 +150,7 @@ class RenameAccountFragment: DialogFragment() { ContentResolver.cancelSync(addrBookAccount, null) // update account name references in database - val db = AppDatabase.getInstance(context) + val db = get<AppDatabase>() try { db.serviceDao().renameAccount(oldAccount.name, newName) } catch (e: Exception) { diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt index c89b1a28..71465a7b 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/SettingsActivity.kt @@ -28,8 +28,8 @@ import androidx.preference.* import at.bitfire.davdroid.InvalidAccountException import at.bitfire.davdroid.PermissionUtils import at.bitfire.davdroid.R -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.Credentials +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.resource.TaskUtils import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.SettingsManager @@ -42,6 +42,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.apache.commons.lang3.StringUtils +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject class SettingsActivity: AppCompatActivity() { @@ -72,10 +74,10 @@ class SettingsActivity: AppCompatActivity() { } - class AccountSettingsFragment: PreferenceFragmentCompat() { + class AccountSettingsFragment: PreferenceFragmentCompat(), KoinComponent { private val account by lazy { requireArguments().getParcelable<Account>(EXTRA_ACCOUNT)!! } - private val settings by lazy { SettingsManager.getInstance(requireActivity()) } + private val settings by inject<SettingsManager>() val model by viewModels<Model>() @@ -370,12 +372,12 @@ class SettingsActivity: AppCompatActivity() { } - class Model(app: Application): AndroidViewModel(app), SyncStatusObserver, SettingsManager.OnChangeListener { + class Model(app: Application): AndroidViewModel(app), KoinComponent, SyncStatusObserver, SettingsManager.OnChangeListener { private var account: Account? = null private var accountSettings: AccountSettings? = null - private val settings = SettingsManager.getInstance(app) + private val settings by inject<SettingsManager>() private var statusChangeListener: Any? = null // settings diff --git a/app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt index 792d2682..7ba2b223 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/account/WebcalFragment.kt @@ -29,14 +29,16 @@ import at.bitfire.davdroid.PermissionUtils import at.bitfire.davdroid.R import at.bitfire.davdroid.closeCompat import at.bitfire.davdroid.databinding.AccountCaldavItemBinding -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Collection +import at.bitfire.davdroid.log.Logger import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.util.logging.Level import kotlin.collections.component1 import kotlin.collections.component2 @@ -154,12 +156,12 @@ class WebcalFragment: CollectionsFragment() { } - class WebcalModel(application: Application): AndroidViewModel(application) { + class WebcalModel(application: Application): AndroidViewModel(application), KoinComponent { private var initialized = false private var serviceId: Long = 0 - private val db = AppDatabase.getInstance(application) + private val db by inject<AppDatabase>() private val resolver = application.contentResolver private var calendarPermission = false diff --git a/app/src/main/java/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt index ca70b74b..66759c3f 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt @@ -30,6 +30,8 @@ import at.bitfire.davdroid.ui.UiUtils import at.bitfire.davdroid.ui.intro.BatteryOptimizationsFragment.Model.Companion.HINT_AUTOSTART_PERMISSION import at.bitfire.davdroid.ui.intro.BatteryOptimizationsFragment.Model.Companion.HINT_BATTERY_OPTIMIZATIONS import org.apache.commons.text.WordUtils +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.util.* class BatteryOptimizationsFragment: Fragment() { @@ -81,7 +83,7 @@ class BatteryOptimizationsFragment: Fragment() { } - class Model(app: Application): AndroidViewModel(app) { + class Model(app: Application): AndroidViewModel(app), KoinComponent { companion object { @@ -129,7 +131,7 @@ class BatteryOptimizationsFragment: Fragment() { true } - val settings = SettingsManager.getInstance(app) + val settings by inject<SettingsManager>() val shouldBeWhitelisted = MutableLiveData<Boolean>() val isWhitelisted = MutableLiveData<Boolean>() diff --git a/app/src/main/java/at/bitfire/davdroid/ui/intro/IntroActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/intro/IntroActivity.kt index cabe2489..a26605c2 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/intro/IntroActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/intro/IntroActivity.kt @@ -14,11 +14,13 @@ import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.davdroid.ui.intro.IIntroFragmentFactory.ShowMode import com.github.appintro.AppIntro2 +import org.koin.core.component.KoinComponent +import org.koin.core.component.get import java.util.* class IntroActivity: AppIntro2() { - companion object { + companion object: KoinComponent { private val serviceLoader = ServiceLoader.load(IIntroFragmentFactory::class.java)!! private val introFragmentFactories = serviceLoader.toList() @@ -29,7 +31,7 @@ class IntroActivity: AppIntro2() { } fun shouldShowIntroActivity(context: Context): Boolean { - val settings = SettingsManager.getInstance(context) + val settings = get<SettingsManager>() return introFragmentFactories.any { val show = it.shouldBeShown(context, settings) Logger.log.fine("Intro fragment $it: showMode=$show") @@ -44,7 +46,7 @@ class IntroActivity: AppIntro2() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val settings = SettingsManager.getInstance(this) + val settings = get<SettingsManager>() val factoriesWithMode = introFragmentFactories.associateWith { it.shouldBeShown(this, settings) } val showAll = factoriesWithMode.values.any { it == ShowMode.SHOW } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/intro/OpenSourceFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/intro/OpenSourceFragment.kt index bc0f4f7b..07af999a 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/intro/OpenSourceFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/intro/OpenSourceFragment.kt @@ -20,6 +20,8 @@ import at.bitfire.davdroid.databinding.IntroOpenSourceBinding import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.davdroid.ui.UiUtils import at.bitfire.davdroid.ui.intro.OpenSourceFragment.Model.Companion.SETTING_NEXT_DONATION_POPUP +import org.koin.core.component.KoinComponent +import org.koin.core.component.get class OpenSourceFragment: Fragment() { @@ -42,14 +44,14 @@ class OpenSourceFragment: Fragment() { } - class Model(app: Application): AndroidViewModel(app) { + class Model(app: Application): AndroidViewModel(app), KoinComponent { companion object { const val SETTING_NEXT_DONATION_POPUP = "time_nextDonationPopup" } val dontShow = object: ObservableBoolean() { - val settings = SettingsManager.getInstance(getApplication()) + val settings = get<SettingsManager>() override fun set(dontShowAgain: Boolean) { if (dontShowAgain) { val nextReminder = System.currentTimeMillis() + 90*86400000L // 90 days (~ 3 months) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt index c669ca1a..35d6671d 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt @@ -24,11 +24,11 @@ import at.bitfire.davdroid.DavService import at.bitfire.davdroid.InvalidAccountException import at.bitfire.davdroid.R import at.bitfire.davdroid.databinding.LoginAccountDetailsBinding -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase 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.settings.AccountSettings import at.bitfire.davdroid.settings.Settings @@ -40,9 +40,12 @@ import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.get +import org.koin.core.component.inject import java.util.logging.Level -class AccountDetailsFragment : Fragment() { +class AccountDetailsFragment : Fragment(), KoinComponent { val loginModel by activityViewModels<LoginModel>() val model by viewModels<AccountDetailsModel>() @@ -63,7 +66,7 @@ class AccountDetailsFragment : Fragment() { ?: loginModel.baseURI?.host // CardDAV-specific - val settings = SettingsManager.getInstance(requireActivity()) + val settings = get<SettingsManager>() v.carddav.visibility = if (config.cardDAV != null) View.VISIBLE else View.GONE if (settings.containsKey(AccountSettings.KEY_CONTACT_GROUP_METHOD)) v.contactGroupMethod.isEnabled = false @@ -134,7 +137,9 @@ class AccountDetailsFragment : Fragment() { class AccountDetailsModel( application: Application - ) : AndroidViewModel(application) { + ) : AndroidViewModel(application), KoinComponent { + + val db by inject<AppDatabase>() val name = MutableLiveData<String>() val nameError = MutableLiveData<String>() @@ -162,11 +167,9 @@ class AccountDetailsFragment : Fragment() { // add entries for account to service DB Logger.log.log(Level.INFO, "Writing account configuration to database", config) - val db = AppDatabase.getInstance(context) try { val accountSettings = AccountSettings(context, account) - val settings = SettingsManager.getInstance(context) - val defaultSyncInterval = settings.getLong(Settings.DEFAULT_SYNC_INTERVAL) + val defaultSyncInterval = get<SettingsManager>().getLong(Settings.DEFAULT_SYNC_INTERVAL) val refreshIntent = Intent(context, DavService::class.java) refreshIntent.action = DavService.ACTION_REFRESH_COLLECTIONS @@ -174,7 +177,7 @@ class AccountDetailsFragment : Fragment() { val addrBookAuthority = context.getString(R.string.address_books_authority) if (config.cardDAV != null) { // insert CardDAV service - val id = insertService(db, name, Service.TYPE_CARDDAV, config.cardDAV) + val id = insertService(name, Service.TYPE_CARDDAV, config.cardDAV) // initial CardDAV account settings accountSettings.setGroupMethod(groupMethod) @@ -191,7 +194,7 @@ class AccountDetailsFragment : Fragment() { if (config.calDAV != null) { // insert CalDAV service - val id = insertService(db, name, Service.TYPE_CALDAV, config.calDAV) + val id = insertService(name, Service.TYPE_CALDAV, config.calDAV) // start CalDAV service detection (refresh collections) refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id) @@ -220,7 +223,7 @@ class AccountDetailsFragment : Fragment() { return result } - private fun insertService(db: AppDatabase, accountName: String, type: String, info: DavResourceFinder.Configuration.ServiceInfo): Long { + private fun insertService(accountName: String, type: String, info: DavResourceFinder.Configuration.ServiceInfo): Long { // insert service val service = Service(0, accountName, type, info.principal) val serviceId = db.serviceDao().insertOrReplace(service) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.kt index e933b56d..f4f4b7bc 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.kt @@ -13,8 +13,8 @@ import at.bitfire.dav4jvm.exception.UnauthorizedException import at.bitfire.dav4jvm.property.* import at.bitfire.davdroid.DavUtils import at.bitfire.davdroid.HttpClient -import at.bitfire.davdroid.log.StringHandler import at.bitfire.davdroid.db.Collection +import at.bitfire.davdroid.log.StringHandler import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.apache.commons.lang3.builder.ReflectionToStringBuilder diff --git a/app/src/main/java/at/bitfire/davdroid/ui/webdav/AddWebdavMountActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/webdav/AddWebdavMountActivity.kt index e9364efc..626d81e1 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/webdav/AddWebdavMountActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/webdav/AddWebdavMountActivity.kt @@ -21,10 +21,10 @@ import at.bitfire.davdroid.App import at.bitfire.davdroid.HttpClient import at.bitfire.davdroid.R import at.bitfire.davdroid.databinding.ActivityAddWebdavMountBinding -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Credentials import at.bitfire.davdroid.db.WebDavMount +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.ui.UiUtils import at.bitfire.davdroid.webdav.CredentialsStore import com.google.android.material.snackbar.Snackbar @@ -33,6 +33,8 @@ import kotlinx.coroutines.launch import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.apache.commons.collections4.CollectionUtils +import org.koin.core.component.KoinComponent +import org.koin.core.component.get import java.net.URI import java.net.URISyntaxException import java.util.logging.Level @@ -135,7 +137,7 @@ class AddWebdavMountActivity: AppCompatActivity() { } - class Model(app: Application): AndroidViewModel(app) { + class Model(app: Application): AndroidViewModel(app), KoinComponent { val displayName = MutableLiveData<String>() val displayNameError = MutableLiveData<String>() @@ -161,8 +163,7 @@ class AddWebdavMountActivity: AppCompatActivity() { return false } - val db = AppDatabase.getInstance(getApplication()) - val id = db.webDavMountDao().insert(mount) + val id = get<AppDatabase>().webDavMountDao().insert(mount) val credentialsStore = CredentialsStore(getApplication()) credentialsStore.setCredentials(id, credentials) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/webdav/WebdavMountsActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/webdav/WebdavMountsActivity.kt index 88e8e5ce..ef5c743d 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/webdav/WebdavMountsActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/webdav/WebdavMountsActivity.kt @@ -32,6 +32,8 @@ import at.bitfire.davdroid.webdav.CredentialsStore import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.apache.commons.io.FileUtils +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject class WebdavMountsActivity: AppCompatActivity() { @@ -164,11 +166,12 @@ class WebdavMountsActivity: AppCompatActivity() { } - class Model(app: Application): AndroidViewModel(app) { + class Model(app: Application): AndroidViewModel(app), KoinComponent { val authority = app.getString(R.string.webdav_authority) - val db = AppDatabase.getInstance(app) + val db by inject<AppDatabase>() + val mountInfos = object: MediatorLiveData<List<MountInfo>>() { var mounts: List<WebDavMount>? = null var roots: List<WebDavDocument>? = null diff --git a/app/src/main/java/at/bitfire/davdroid/webdav/DavDocumentsProvider.kt b/app/src/main/java/at/bitfire/davdroid/webdav/DavDocumentsProvider.kt index 618f362f..e3352f90 100644 --- a/app/src/main/java/at/bitfire/davdroid/webdav/DavDocumentsProvider.kt +++ b/app/src/main/java/at/bitfire/davdroid/webdav/DavDocumentsProvider.kt @@ -15,7 +15,10 @@ import android.graphics.BitmapFactory import android.graphics.Point import android.media.ThumbnailUtils import android.net.ConnectivityManager -import android.os.* +import android.os.Build +import android.os.Bundle +import android.os.CancellationSignal +import android.os.ParcelFileDescriptor import android.os.storage.StorageManager import android.provider.DocumentsContract.* import android.provider.DocumentsProvider @@ -31,21 +34,23 @@ import at.bitfire.dav4jvm.property.* import at.bitfire.davdroid.HttpClient import at.bitfire.davdroid.MemoryCookieStore import at.bitfire.davdroid.R +import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.db.DaoTools +import at.bitfire.davdroid.db.WebDavDocument +import at.bitfire.davdroid.db.WebDavMount import at.bitfire.davdroid.log.Logger -import at.bitfire.davdroid.db.* import at.bitfire.davdroid.ui.webdav.WebdavMountsActivity import at.bitfire.davdroid.webdav.cache.HeadResponseCache import okhttp3.CookieJar import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.logging.HttpLoggingInterceptor +import org.koin.android.ext.android.inject import java.io.ByteArrayOutputStream import java.io.FileNotFoundException import java.net.HttpURLConnection import java.util.concurrent.* import java.util.logging.Level -import kotlin.collections.ArrayList -import kotlin.collections.HashMap import kotlin.math.min class DavDocumentsProvider: DocumentsProvider() { @@ -68,7 +73,7 @@ class DavDocumentsProvider: DocumentsProvider() { lateinit var authority: String - private val db: AppDatabase by lazy { AppDatabase.getInstance(context!!) } + private val db: AppDatabase by inject() private val mountDao by lazy { db.webDavMountDao() } private val documentDao by lazy { db.webDavDocumentDao() } private val webdavMountsLive by lazy { mountDao.getAllLive() } diff --git a/app/src/main/java/at/bitfire/davdroid/webdav/RandomAccessCallback.kt b/app/src/main/java/at/bitfire/davdroid/webdav/RandomAccessCallback.kt index 4db3fb72..e0406f79 100644 --- a/app/src/main/java/at/bitfire/davdroid/webdav/RandomAccessCallback.kt +++ b/app/src/main/java/at/bitfire/davdroid/webdav/RandomAccessCallback.kt @@ -30,9 +30,12 @@ import okhttp3.HttpUrl import okhttp3.MediaType import org.apache.commons.io.FileUtils import java.io.InterruptedIOException +import java.lang.ref.WeakReference import java.net.HttpURLConnection import java.util.logging.Level +typealias MemorySegmentCache = MemoryCache<SegmentedCache.SegmentKey<RandomAccessCallback.DocumentKey>> + @TargetApi(26) class RandomAccessCallback private constructor( val context: Context, @@ -46,6 +49,25 @@ class RandomAccessCallback private constructor( companion object { /** one GET request per 2 MB */ const val PAGE_SIZE: Int = (2*FileUtils.ONE_MB).toInt() + + private var _memoryCache: WeakReference<MemorySegmentCache>? = null + + @Synchronized + fun getMemoryCache(context: Context): MemorySegmentCache { + val cache = _memoryCache?.get() + if (cache != null) + return cache + + Logger.log.info("Creating memory cache") + + val maxHeapSizeMB = ContextCompat.getSystemService(context, ActivityManager::class.java)!!.memoryClass + val cacheSize = maxHeapSizeMB * FileUtils.ONE_MB.toInt() / 2 + val newCache = MemorySegmentCache(cacheSize) + + _memoryCache = WeakReference(newCache) + return newCache + } + } private val dav = DavResource(httpClient.okHttpClient, url) @@ -67,11 +89,7 @@ class RandomAccessCallback private constructor( private val workerThread = HandlerThread(javaClass.simpleName).apply { start() } val workerHandler: Handler = Handler(workerThread.looper) - val memoryCache = Singleton.getInstance(context) { appContext -> - val maxHeapSizeMB = ContextCompat.getSystemService(appContext, ActivityManager::class.java)!!.memoryClass - val cacheSize = maxHeapSizeMB * FileUtils.ONE_MB.toInt() / 2 - MemoryCache<SegmentedCache.SegmentKey<DocumentKey>>(cacheSize) - } + val memoryCache = getMemoryCache(context) val cache = SegmentedCache(PAGE_SIZE, this, memoryCache) diff --git a/app/src/main/java/at/bitfire/davdroid/webdav/ThumbnailCache.kt b/app/src/main/java/at/bitfire/davdroid/webdav/ThumbnailCache.kt index 3a73eb73..cb8f4c13 100644 --- a/app/src/main/java/at/bitfire/davdroid/webdav/ThumbnailCache.kt +++ b/app/src/main/java/at/bitfire/davdroid/webdav/ThumbnailCache.kt @@ -10,8 +10,8 @@ import android.os.Build import android.os.storage.StorageManager import androidx.annotation.WorkerThread import androidx.core.content.ContextCompat -import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.db.WebDavDocument +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.webdav.cache.CacheUtils import at.bitfire.davdroid.webdav.cache.DiskCache import org.apache.commons.io.FileUtils diff --git a/app/src/test/java/at/bitfire/davdroid/SingletonTest.kt b/app/src/test/java/at/bitfire/davdroid/SingletonTest.kt deleted file mode 100644 index 2a3b7f74..00000000 --- a/app/src/test/java/at/bitfire/davdroid/SingletonTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -/*************************************************************************************************** - * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. - **************************************************************************************************/ - -package at.bitfire.davdroid - -import org.junit.Assert.* -import org.junit.Before -import org.junit.Test -import java.lang.ref.WeakReference - -class SingletonTest { - - @Before - fun prepare() { - Singleton.dropAll() - } - - - @Test - fun testCache() { - val obj1 = Singleton.getInstance { Any() } - assertEquals(obj1, Singleton.getInstance { - fail("No new Any must be created") - Any() - }) - } - - @Test - fun testCacheUsesWeakReferences() { - var obj1: Any? = Singleton.getInstance { Any() } - val refObj1 = WeakReference(obj1) - obj1 = null - - // no reference anymore, validate - System.gc() - Runtime.getRuntime().gc() - assertNull(refObj1.get()) - - // create a new instance - val obj2 = Singleton.getInstance { Any() } - assertEquals(obj2, Singleton.getInstance { - fail("No new Any must be created") - Any() - }) - } - - @Test(expected = IllegalStateException::class) - fun testRecursive() { - Singleton.getInstance() { - Singleton.getInstance() { - Any() - } - } - } - -}
\ No newline at end of file |