Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions utils/src/main/kotlin/de/cyface/utils/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
*/
package de.cyface.utils

import android.content.Context
import android.os.Environment

/**
* Final static constants used by multiple classes.
*
Expand All @@ -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
}
}
13 changes: 8 additions & 5 deletions utils/src/main/kotlin/de/cyface/utils/DiskConsumption.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
}
}
}
64 changes: 64 additions & 0 deletions utils/src/main/kotlin/de/cyface/utils/StorageHelper.kt
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}
Loading