diff --git a/utils/src/main/kotlin/de/cyface/utils/Constants.kt b/utils/src/main/kotlin/de/cyface/utils/Constants.kt index 5bc76c5..8fa8de7 100644 --- a/utils/src/main/kotlin/de/cyface/utils/Constants.kt +++ b/utils/src/main/kotlin/de/cyface/utils/Constants.kt @@ -18,9 +18,6 @@ */ package de.cyface.utils -import android.content.Context -import android.os.Environment - /** * Final static constants used by multiple classes. * @@ -39,20 +36,4 @@ object Constants { * slow down the device and could get unusable. */ const val MINIMUM_MEGABYTES_REQUIRED = 100L - - /** - * The path where files can be stored by the SDK, e.g. image material. - * - * **Attention:** This data *is deleted* when the app is uninstalled. - */ - @Suppress("unused") - fun externalCyfaceFolderPath(context: Context): String { - val paths = context.getExternalFilesDirs(null) - Validate.isTrue(paths.isNotEmpty(), "No external storage available") - val directory = paths[0] - val isExternalStorageMounted = - Environment.getExternalStorageState(directory) == Environment.MEDIA_MOUNTED - check(isExternalStorageMounted) { "External storage is not mounted" } - return directory.path - } } \ No newline at end of file diff --git a/utils/src/main/kotlin/de/cyface/utils/DiskConsumption.kt b/utils/src/main/kotlin/de/cyface/utils/DiskConsumption.kt index 9dc1261..1c8e905 100644 --- a/utils/src/main/kotlin/de/cyface/utils/DiskConsumption.kt +++ b/utils/src/main/kotlin/de/cyface/utils/DiskConsumption.kt @@ -18,7 +18,7 @@ */ package de.cyface.utils -import android.os.Environment +import android.content.Context import android.os.Parcel import android.os.Parcelable import android.os.Parcelable.Creator @@ -131,10 +131,12 @@ class DiskConsumption : Parcelable { /** * Checks how much storage is left. * + * @param context The Android context. * @return The number of bytes of space available. */ - private fun bytesAvailable(): Long { - val stat = StatFs(Environment.getExternalStorageDirectory().path) + private fun bytesAvailable(context: Context): Long { + val storageDir = StorageHelper.getStorageDirectoryWithFallback(context) + val stat = StatFs(storageDir.path) val bytesAvailable = stat.blockSizeLong * stat.availableBlocksLong Log.v(TAG, "Space available: " + (bytesAvailable / (1024 * 1024)) + " MB") return bytesAvailable @@ -143,11 +145,12 @@ class DiskConsumption : Parcelable { /** * Checks if at last `MINIMUM_MEGABYTES_REQUIRED` of space is available. * + * @param context The Android context. * @return True if enough space is available. */ @Suppress("unused") // Part of the API - fun spaceAvailable(): Boolean { - return (bytesAvailable() / (1024 * 1024)) > MINIMUM_MEGABYTES_REQUIRED + fun spaceAvailable(context: Context): Boolean { + return (bytesAvailable(context) / (1024 * 1024)) > MINIMUM_MEGABYTES_REQUIRED } } } diff --git a/utils/src/main/kotlin/de/cyface/utils/StorageHelper.kt b/utils/src/main/kotlin/de/cyface/utils/StorageHelper.kt new file mode 100644 index 0000000..e83eed3 --- /dev/null +++ b/utils/src/main/kotlin/de/cyface/utils/StorageHelper.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2025 Cyface GmbH + * + * This file is part of the Cyface Utils for Android. + * + * The Cyface Utils for Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Cyface Utils for Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Cyface Utils for Android. If not, see . + */ +package de.cyface.utils + +import android.content.Context +import android.os.Environment +import android.util.Log +import de.cyface.utils.Constants.TAG +import java.io.File + +/** + * Helper class for storage-related operations. + * + * @author Armin Schnabel + * @version 1.0.0 + * @since 4.3.0 + */ +object StorageHelper { + /** + * Gets the storage directory for the app, trying primary external storage first and falling + * back to internal storage if external is not available or not writable. + * + * This method only uses the primary external storage (the first entry in getExternalFilesDirs), + * which is typically the device's main app-specific external storage directory. It does not use + * secondary external storage like removable SD cards or USB drives, which appear at higher + * indices and could be unreliable (e.g., user might unplug them). + * + * @param context The Android context. + * @return The storage directory (primary external if available and writable, otherwise internal). + */ + fun getStorageDirectoryWithFallback(context: Context): File { + // Try primary external storage only (first entry) + val externalDirs = context.getExternalFilesDirs(null) + if (externalDirs.isNotEmpty() && externalDirs[0] != null) { + val directory = externalDirs[0] + val isExternalStorageMounted = + Environment.getExternalStorageState(directory) == Environment.MEDIA_MOUNTED + if (isExternalStorageMounted && directory.canWrite()) { + Log.d(TAG, "Using primary external storage: ${directory.absolutePath}") + return directory + } + } + + // Fall back to internal storage + Log.d(TAG, "Falling back to internal storage: ${context.filesDir.absolutePath}") + return context.filesDir + } +}