diff --git a/app/src/main/java/com/itsaky/androidide/activities/SplashActivity.kt b/app/src/main/java/com/itsaky/androidide/activities/SplashActivity.kt
index c729e68512..d5d8dc48ad 100644
--- a/app/src/main/java/com/itsaky/androidide/activities/SplashActivity.kt
+++ b/app/src/main/java/com/itsaky/androidide/activities/SplashActivity.kt
@@ -23,7 +23,9 @@ import android.os.Build
import android.os.Bundle
import com.itsaky.androidide.app.configuration.CpuArch
import com.itsaky.androidide.app.configuration.IDEBuildConfigProvider
+import com.itsaky.androidide.ui.showLowStorageDialog
import com.itsaky.androidide.utils.FeatureFlags
+import com.itsaky.androidide.utils.hasEnoughStorageAvailable
import kotlin.system.exitProcess
/**
@@ -33,6 +35,11 @@ class SplashActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ if (!hasEnoughStorageAvailable()) {
+ showLowStorageDialog()
+ return
+ }
+
val isX86 = Build.SUPPORTED_ABIS.firstOrNull() in listOf(CpuArch.X86_64.abi, CpuArch.X86.abi)
if (isX86 && (!IDEBuildConfigProvider.getInstance().supportsCpuAbi() || !FeatureFlags.isEmulatorUseEnabled)) {
diff --git a/app/src/main/java/com/itsaky/androidide/ui/StorageDialog.kt b/app/src/main/java/com/itsaky/androidide/ui/StorageDialog.kt
new file mode 100644
index 0000000000..b97d6cabf6
--- /dev/null
+++ b/app/src/main/java/com/itsaky/androidide/ui/StorageDialog.kt
@@ -0,0 +1,33 @@
+package com.itsaky.androidide.ui
+
+import android.app.Activity
+import android.app.AlertDialog
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.provider.Settings
+import com.itsaky.androidide.resources.R
+import com.itsaky.androidide.utils.getMinimumStorageNeeded
+
+
+fun Activity.showLowStorageDialog() {
+ val appName = getString(R.string.app_name)
+ val minSpace = getMinimumStorageNeeded()
+
+ AlertDialog.Builder(this)
+ .setTitle(R.string.err_insufficient_storage_title)
+ .setMessage(getString(R.string.err_insufficient_storage_msg, appName, minSpace))
+ .setCancelable(false)
+ .setPositiveButton(getString(R.string.action_close_app, appName)) { _, _ ->
+ finishAndRemoveTask()
+ }
+ .setNegativeButton(R.string.action_free_up_space) { _, _ ->
+ val intent = Intent(Settings.ACTION_INTERNAL_STORAGE_SETTINGS)
+ try {
+ startActivity(intent)
+ } catch (_: ActivityNotFoundException) {
+ startActivity(Intent(Settings.ACTION_SETTINGS))
+ }
+ finishAffinity()
+ }
+ .show()
+}
diff --git a/app/src/main/java/com/itsaky/androidide/utils/StorageUtils.kt b/app/src/main/java/com/itsaky/androidide/utils/StorageUtils.kt
new file mode 100644
index 0000000000..41ef2d67f1
--- /dev/null
+++ b/app/src/main/java/com/itsaky/androidide/utils/StorageUtils.kt
@@ -0,0 +1,23 @@
+package com.itsaky.androidide.utils
+
+import android.os.Environment
+import android.os.StatFs
+
+fun hasEnoughStorageAvailable(): Boolean {
+ return try {
+ val dataDir = Environment.getDataDirectory()
+ val stat = StatFs(dataDir.path)
+ val availableBytes = stat.availableBlocksLong * stat.blockSizeLong
+ availableBytes > getMinimumStorageNeeded().gigabytesToBytes()
+ } catch (_: Exception) { false }
+}
+
+fun getMinimumStorageNeeded(): Long {
+ val minimumStorageStableGB = 4L
+ val minimumStorageExperimentalGB = 6L
+
+ if (FeatureFlags.isExperimentsEnabled) {
+ return minimumStorageExperimentalGB;
+ }
+ return minimumStorageStableGB;
+}
diff --git a/app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt b/app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt
index 11b082894e..95c1f7e714 100644
--- a/app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt
+++ b/app/src/main/java/com/itsaky/androidide/viewmodel/InstallationViewModel.kt
@@ -11,8 +11,8 @@ import com.itsaky.androidide.events.InstallationEvent
import com.itsaky.androidide.models.StorageInfo
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.utils.Environment
-import com.itsaky.androidide.utils.FeatureFlags
import com.itsaky.androidide.utils.bytesToGigabytes
+import com.itsaky.androidide.utils.getMinimumStorageNeeded
import com.itsaky.androidide.utils.gigabytesToBytes
import com.itsaky.androidide.utils.withStopWatch
import com.itsaky.androidide.viewmodel.InstallationState.InstallationComplete
@@ -34,17 +34,6 @@ import kotlinx.coroutines.withContext
import org.slf4j.LoggerFactory
class InstallationViewModel : ViewModel() {
- companion object {
- private const val MINIMUM_STORAGE_STABLE_GB = 4L
- private const val MINIMUM_STORAGE_EXPERIMENTAL_GB = 6L
- }
-
- private val minimumStorageNeeded: Long
- get() = if (FeatureFlags.isExperimentsEnabled) {
- MINIMUM_STORAGE_EXPERIMENTAL_GB
- } else {
- MINIMUM_STORAGE_STABLE_GB
- }
private val log = LoggerFactory.getLogger(InstallationViewModel::class.java)
@@ -148,7 +137,7 @@ class InstallationViewModel : ViewModel() {
val stat = StatFs(internalStoragePath)
val availableStorageInBytes = stat.availableBlocksLong * stat.blockSizeLong
- val requiredStorageInBytes = minimumStorageNeeded.gigabytesToBytes()
+ val requiredStorageInBytes = getMinimumStorageNeeded().gigabytesToBytes()
val isLowStorage = availableStorageInBytes < requiredStorageInBytes
diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml
index ccc30cd1dd..f2aefb5ebb 100644
--- a/resources/src/main/res/values/strings.xml
+++ b/resources/src/main/res/values/strings.xml
@@ -27,6 +27,10 @@
App Logs
IDE Logs
General information about this feature.
+ Insufficient Storage
+ %1$s requires at least %2$dGB of free space to run and extract the necessary tools.\n\nPlease free up space on your device and try again.
+ Close %1$s
+ Free Up Space
🤖 System: %1$s backend selected.