diff --git a/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallExceptions.kt b/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallExceptions.kt new file mode 100644 index 0000000000..c3e9e8ff71 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallExceptions.kt @@ -0,0 +1,3 @@ +package com.itsaky.androidide.assets + +class MissingAssetsEntryException(cause: Throwable) : Exception(cause) diff --git a/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt b/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt index 3f094f5e2e..792dd0eae9 100644 --- a/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt +++ b/app/src/main/java/com/itsaky/androidide/assets/AssetsInstallationHelper.kt @@ -77,14 +77,19 @@ object AssetsInstallationHelper { } if (result.isFailure) { - val e = result.exceptionOrNull() - if (e is CancellationException) { - throw e + val e = result.exceptionOrNull() ?: RuntimeException(context.getString(R.string.error_installation_failed)) + if (e is CancellationException) throw e + + val isMissingAsset = generateSequence(e) { it.cause }.any { it is FileNotFoundException } + val cause = if (isMissingAsset) MissingAssetsEntryException(e) else e + val msg = if (isMissingAsset) { + context.getString(R.string.err_missing_or_corrupt_assets, context.getString(R.string.app_name)) + } else { + e.message ?: context.getString(R.string.error_installation_failed) } - val msg = e?.message ?: "Failed to install assets" logger.error("Failed to install assets", e) onProgress(Progress(msg)) - return@withContext Result.Failure(e, errorMessage = msg) + return@withContext Result.Failure(cause, errorMessage = msg, shouldReportToSentry = !isMissingAsset) } return@withContext Result.Success diff --git a/app/src/test/java/com/itsaky/androidide/assets/AssetsInstallationHelperTest.kt b/app/src/test/java/com/itsaky/androidide/assets/AssetsInstallationHelperTest.kt new file mode 100644 index 0000000000..81660f0ca9 --- /dev/null +++ b/app/src/test/java/com/itsaky/androidide/assets/AssetsInstallationHelperTest.kt @@ -0,0 +1,51 @@ +package com.itsaky.androidide.assets + +import android.content.Context +import com.itsaky.androidide.assets.AssetsInstallationHelper.Result.Failure +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import java.io.FileNotFoundException + +class AssetsInstallationHelperTest { + + private val ctx: Context = mockk(relaxed = true) + + @Before + fun setup() { + mockkObject(AssetsInstallationHelper) + } + + @Test + fun `install with missing asset skips sentry`() = runBlocking { + val helper = AssetsInstallationHelper + + every { + helper["checkStorageAccessibility"](any(), any()) + } returns null + + coEvery { + helper["doInstall"](any(), any()) + } throws FileNotFoundException("data/common/gradle.zip.br") + + val result = helper.install(ctx) + + assertTrue("Expected Result.Failure", result is Failure) + val failure = result as Failure + assertFalse("Should skip Sentry report", failure.shouldReportToSentry) + assertTrue( + "Expected MissingAssetsEntryException as cause", + failure.cause is MissingAssetsEntryException + ) + assertTrue( + "Expected FileNotFoundException as root cause", + (failure.cause?.cause) is FileNotFoundException + ) + } +} diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index f2aefb5ebb..a35f919bd1 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -96,7 +96,9 @@ No references found Diagnostics Installation failed + Failed to install assets Entry \'%1$s\' not found in assets zip file + Missing installation files. %1$s installation might be corrupt or incomplete. The picked file is not a directory. Please wait for a moment. Not enough storage available for installation. An additional %1$.1fGB is required on the internal storage partition. You currently have %2$.1fGB available.