Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Verified Commit a0e8d734 authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

refactor: remove detekt's @Suppress("TooGenericExceptionCaught") from InstallOrchestrator

Switched to Kotlin's run-catching{} with additional handling of CancellationException and throwables.
parent 9dd6e484
Loading
Loading
Loading
Loading
+41 −24
Original line number Diff line number Diff line
@@ -37,7 +37,6 @@ import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject

@Suppress("TooGenericExceptionCaught")
class InstallOrchestrator @Inject constructor(
    @param:ApplicationContext val context: Context,
    @param:IoCoroutineScope private val scope: CoroutineScope,
@@ -47,12 +46,14 @@ class InstallOrchestrator @Inject constructor(

    fun init() {
        scope.launch {
            try {
            runCatching {
                cancelFailedDownloads()
            } catch (cancellationException: CancellationException) {
                throw cancellationException
            } catch (e: Exception) {
                Timber.e(e, "Failed to reconcile startup downloads")
            }.onFailure { throwable ->
                when (throwable) {
                    is CancellationException -> throw throwable
                    is Exception -> Timber.e(throwable, "Failed to reconcile startup downloads")
                    else -> throw throwable
                }
            }
            observeDownloads()
        }
@@ -60,13 +61,17 @@ class InstallOrchestrator @Inject constructor(

    private fun observeDownloads() {
        installDao.getDownloads().onEach { list ->
            try {
            runCatching {
                if (list.none { it.status == Status.DOWNLOADING || it.status == Status.INSTALLING }) {
                    list.find { it.status == Status.AWAITING }
                        ?.let { queuedDownload -> trigger(queuedDownload) }
                }
            } catch (e: Exception) {
                Timber.e(e, "Failed to enqueue download worker")
            }.onFailure { throwable ->
                when (throwable) {
                    is CancellationException -> throw throwable
                    is Exception -> Timber.e(throwable, "Failed to enqueue download worker")
                    else -> throw throwable
                }
            }
        }.launchIn(scope)
    }
@@ -74,19 +79,24 @@ class InstallOrchestrator @Inject constructor(
    private suspend fun trigger(download: AppInstall) {
        val uniqueWorkName = InstallWorkManager.getUniqueWorkName(download.packageName)

        try {
        runCatching {
            val operation = InstallWorkManager.enqueueWork(download, false)
            operation.await()
            Timber.d("INSTALL: Successfully enqueued unique work for ${download.name}: $uniqueWorkName")
        } catch (cancellationException: CancellationException) {
            throw cancellationException
        } catch (e: Exception) {
        }.onFailure { throwable ->
            when (throwable) {
                is CancellationException -> throw throwable
                is Exception -> {
                    Timber.e(
                e,
                        throwable,
                        "INSTALL: Failed to enqueue unique work for ${download.name}: $uniqueWorkName"
                    )
                    appManagerWrapper.installationIssue(download)
                }

                else -> throw throwable
            }
        }
    }

    private suspend fun cancelFailedDownloads() {
@@ -106,7 +116,7 @@ class InstallOrchestrator @Inject constructor(
                Status.INSTALLING
            )
        }.forEach { app ->
            try {
            runCatching {
                val workInfos = workManager.getWorkInfosByTagFlow(app.id).firstOrNull().orEmpty()
                val hasActiveWork = workInfos.any { info -> info.state in activeWorkStates }
                val hasActiveSession =
@@ -125,12 +135,19 @@ class InstallOrchestrator @Inject constructor(
                        appManagerWrapper.installationIssue(app)
                    }
                }
            } catch (e: Exception) {
            }.onFailure { throwable ->
                when (throwable) {
                    is CancellationException -> throw throwable
                    is Exception -> {
                        Timber.e(
                    e,
                            throwable,
                            "INSTALL: Failed to reconcile startup state for ${app.name}/${app.packageName}"
                        )
                    }

                    else -> throw throwable
                }
            }
        }
    }

+22 −0
Original line number Diff line number Diff line
@@ -195,6 +195,21 @@ class InstallOrchestratorTest {
        verifyMockito(appManagerWrapper, times(1)).installationIssue(eq(awaiting))
    }

    @Test
    fun init_doesNotReportIssueWhenEnqueueIsCancelled() = runTest {
        val awaiting = createAppInstall(id = "app.awaiting.cancelled", status = Status.AWAITING)
        mockInstallWorkManagerCancellation()

        whenever(installDao.getDownloads()).thenReturn(flowOf(emptyList()), flowOf(listOf(awaiting)))

        val installOrchestrator = InstallOrchestrator(context, this, appManagerWrapper, installDao)

        installOrchestrator.init()
        advanceUntilIdle()

        verifyMockito(appManagerWrapper, never()).installationIssue(any())
    }

    @Test
    fun init_skipsReconciliationWhenActiveWorkExists() = runTest {
        val app = createAppInstall(id = "app.active.work", status = Status.DOWNLOADING)
@@ -274,6 +289,13 @@ class InstallOrchestratorTest {
        every { InstallWorkManager.enqueueWork(any(), any()) } throws RuntimeException("enqueue failed")
    }

    private fun mockInstallWorkManagerCancellation() {
        mockkObject(InstallWorkManager)
        isInstallWorkManagerMocked = true
        every { InstallWorkManager.getUniqueWorkName(any()) } answers { callOriginal() }
        every { InstallWorkManager.enqueueWork(any(), any()) } throws CancellationException("enqueue cancelled")
    }

    private fun successfulOperation(): Operation {
        val operation = mock<Operation>()
        whenever(operation.result).thenReturn(Futures.immediateFuture(Operation.SUCCESS))