Avoid memory leak in ExceptionResolver

(cherry picked from commit 7a3b2a9bb4)
devel
Koitharu 6 months ago
parent 881f154b5e
commit cceaefc896
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -8,9 +8,10 @@ import androidx.collection.MutableScatterMap
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import dagger.assisted.Assisted import androidx.lifecycle.Lifecycle
import dagger.assisted.AssistedFactory import androidx.lifecycle.LifecycleOwner
import dagger.assisted.AssistedInject import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.async
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.BrowserActivity import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity
@ -32,14 +33,15 @@ import org.koitharu.kotatsu.scrobbling.common.domain.ScrobblerAuthRequiredExcept
import org.koitharu.kotatsu.scrobbling.common.ui.ScrobblerAuthHelper import org.koitharu.kotatsu.scrobbling.common.ui.ScrobblerAuthHelper
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
import java.security.cert.CertPathValidatorException import java.security.cert.CertPathValidatorException
import javax.inject.Inject
import javax.inject.Provider import javax.inject.Provider
import javax.net.ssl.SSLException import javax.net.ssl.SSLException
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
class ExceptionResolver @AssistedInject constructor( class ExceptionResolver private constructor(
@Assisted private val host: Host, private val host: Host,
private val settings: AppSettings, private val settings: AppSettings,
private val scrobblerAuthHelperProvider: Provider<ScrobblerAuthHelper>, private val scrobblerAuthHelperProvider: Provider<ScrobblerAuthHelper>,
) { ) {
@ -56,10 +58,11 @@ class ExceptionResolver @AssistedInject constructor(
} }
fun showErrorDetails(e: Throwable, url: String? = null) { fun showErrorDetails(e: Throwable, url: String? = null) {
host.router()?.showErrorDialog(e, url) host.router.showErrorDialog(e, url)
} }
suspend fun resolve(e: Throwable): Boolean = when (e) { suspend fun resolve(e: Throwable): Boolean = host.lifecycleScope.async {
when (e) {
is CloudFlareProtectedException -> resolveCF(e) is CloudFlareProtectedException -> resolveCF(e)
is AuthRequiredException -> resolveAuthException(e.source) is AuthRequiredException -> resolveAuthException(e.source)
is SSLException, is SSLException,
@ -71,7 +74,7 @@ class ExceptionResolver @AssistedInject constructor(
is InteractiveActionRequiredException -> resolveBrowserAction(e) is InteractiveActionRequiredException -> resolveBrowserAction(e)
is ProxyConfigException -> { is ProxyConfigException -> {
host.router()?.openProxySettings() host.router.openProxySettings()
false false
} }
@ -99,6 +102,7 @@ class ExceptionResolver @AssistedInject constructor(
else -> false else -> false
} }
}.await()
private suspend fun resolveBrowserAction( private suspend fun resolveBrowserAction(
e: InteractiveActionRequiredException e: InteractiveActionRequiredException
@ -118,11 +122,11 @@ class ExceptionResolver @AssistedInject constructor(
} }
private fun openInBrowser(url: String) { private fun openInBrowser(url: String) {
host.router()?.openBrowser(url, null, null) host.router.openBrowser(url, null, null)
} }
private fun openAlternatives(manga: Manga) { private fun openAlternatives(manga: Manga) {
host.router()?.openAlternatives(manga) host.router.openAlternatives(manga)
} }
private fun handleActivityResult(tag: String, result: Boolean) { private fun handleActivityResult(tag: String, result: Boolean) {
@ -130,7 +134,7 @@ class ExceptionResolver @AssistedInject constructor(
} }
private fun showSslErrorDialog() { private fun showSslErrorDialog() {
val ctx = host.getContext() ?: return val ctx = host.context ?: return
if (settings.isSSLBypassEnabled) { if (settings.isSSLBypassEnabled) {
Toast.makeText(ctx, R.string.operation_not_supported, Toast.LENGTH_SHORT).show() Toast.makeText(ctx, R.string.operation_not_supported, Toast.LENGTH_SHORT).show()
return return
@ -147,27 +151,65 @@ class ExceptionResolver @AssistedInject constructor(
}.show() }.show()
} }
private inline fun Host.withContext(block: Context.() -> Unit) { class Factory @Inject constructor(
getContext()?.apply(block) private val settings: AppSettings,
private val scrobblerAuthHelperProvider: Provider<ScrobblerAuthHelper>,
) {
fun create(fragment: Fragment) = ExceptionResolver(
host = Host.FragmentHost(fragment),
settings = settings,
scrobblerAuthHelperProvider = scrobblerAuthHelperProvider,
)
fun create(activity: FragmentActivity) = ExceptionResolver(
host = Host.ActivityHost(activity),
settings = settings,
scrobblerAuthHelperProvider = scrobblerAuthHelperProvider,
)
} }
private fun Host.router(): AppRouter? = when (this) { private sealed interface Host : ActivityResultCaller, LifecycleOwner {
is FragmentActivity -> router
is Fragment -> router val context: Context?
else -> null
val router: AppRouter
val fragmentManager: FragmentManager
inline fun withContext(block: Context.() -> Unit) {
context?.apply(block)
} }
interface Host : ActivityResultCaller { class ActivityHost(val activity: FragmentActivity) : Host,
ActivityResultCaller by activity,
LifecycleOwner by activity {
fun getChildFragmentManager(): FragmentManager override val context: Context
get() = activity
fun getContext(): Context? override val router: AppRouter
get() = activity.router
override val fragmentManager: FragmentManager
get() = activity.supportFragmentManager
} }
@AssistedFactory class FragmentHost(val fragment: Fragment) : Host,
interface Factory { ActivityResultCaller by fragment {
override val context: Context?
get() = fragment.context
fun create(host: Host): ExceptionResolver override val router: AppRouter
get() = fragment.router
override val fragmentManager: FragmentManager
get() = fragment.childFragmentManager
override val lifecycle: Lifecycle
get() = fragment.viewLifecycleOwner.lifecycle
}
} }
companion object { companion object {

@ -33,7 +33,6 @@ import androidx.appcompat.R as appcompatR
abstract class BaseActivity<B : ViewBinding> : abstract class BaseActivity<B : ViewBinding> :
AppCompatActivity(), AppCompatActivity(),
ExceptionResolver.Host,
OnApplyWindowInsetsListener, OnApplyWindowInsetsListener,
ScreenshotPolicyHelper.ContentContainer { ScreenshotPolicyHelper.ContentContainer {
@ -87,10 +86,6 @@ abstract class BaseActivity<B : ViewBinding> :
@Deprecated("Use ViewBinding", level = DeprecationLevel.ERROR) @Deprecated("Use ViewBinding", level = DeprecationLevel.ERROR)
override fun setContentView(view: View?) = throw UnsupportedOperationException() override fun setContentView(view: View?) = throw UnsupportedOperationException()
override fun getContext() = this
override fun getChildFragmentManager(): FragmentManager = supportFragmentManager
protected fun setContentView(binding: B) { protected fun setContentView(binding: B) {
this.viewBinding = binding this.viewBinding = binding
super.setContentView(binding.root) super.setContentView(binding.root)

@ -15,8 +15,7 @@ import org.koitharu.kotatsu.core.ui.util.ActionModeDelegate
abstract class BaseFragment<B : ViewBinding> : abstract class BaseFragment<B : ViewBinding> :
OnApplyWindowInsetsListener, OnApplyWindowInsetsListener,
Fragment(), Fragment() {
ExceptionResolver.Host {
var viewBinding: B? = null var viewBinding: B? = null
private set private set

@ -36,8 +36,7 @@ import com.google.android.material.R as materialR
abstract class BasePreferenceFragment(@StringRes private val titleId: Int) : abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
PreferenceFragmentCompat(), PreferenceFragmentCompat(),
OnApplyWindowInsetsListener, OnApplyWindowInsetsListener,
RecyclerViewOwner, RecyclerViewOwner {
ExceptionResolver.Host {
protected lateinit var exceptionResolver: ExceptionResolver protected lateinit var exceptionResolver: ExceptionResolver
private set private set

@ -32,8 +32,7 @@ import org.koitharu.kotatsu.core.ui.util.ActionModeDelegate
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
abstract class BaseAdaptiveSheet<B : ViewBinding> : AppCompatDialogFragment(), abstract class BaseAdaptiveSheet<B : ViewBinding> : AppCompatDialogFragment(),
OnApplyWindowInsetsListener, OnApplyWindowInsetsListener {
ExceptionResolver.Host {
private var waitingForDismissAllowingStateLoss = false private var waitingForDismissAllowingStateLoss = false
private var isFitToContentsDisabled = false private var isFitToContentsDisabled = false

Loading…
Cancel
Save