feat: in-app test file generator for upload testing

adb-pushed files are invisible to the app due to scoped storage, so add a
'Create 120 MB test file' button in the Downloads tab that writes the file
through the app's own uid (visible in its contribution set).
This commit is contained in:
Mathieu Moran
2026-08-12 18:23:37 -04:00
parent 6f112ab622
commit 6398baaa6b
3 changed files with 31 additions and 0 deletions
@@ -19,7 +19,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.kotlinx.serialization.asConverterFactory import retrofit2.converter.kotlinx.serialization.asConverterFactory
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
@@ -257,6 +259,24 @@ class DashcamViewModel(application: Application) : AndroidViewModel(application)
_downloadedFiles.value = files _downloadedFiles.value = files
} }
fun generateTestFile(sizeMb: Int = 120) {
viewModelScope.launch {
val f = File(getDownloadDir(), "test-upload-${sizeMb}mb.mp4")
try {
withContext(Dispatchers.IO) {
f.outputStream().use { out ->
val buf = ByteArray(1 shl 20) // 1MB
repeat(sizeMb) { out.write(buf) }
}
}
scanDownloads()
_downloadEvents.emit("✓ Created test file: ${f.name} (${sizeMb} MB)")
} catch (e: Exception) {
_downloadEvents.emit("✗ Could not create test file: ${e.message}")
}
}
}
fun deleteDownloaded(file: DownloadedFile) { fun deleteDownloaded(file: DownloadedFile) {
_deletingNames.value = _deletingNames.value + file.name _deletingNames.value = _deletingNames.value + file.name
viewModelScope.launch { viewModelScope.launch {
@@ -67,6 +67,7 @@ fun DownloadsScreen(
onCancel: (ActiveDownload) -> Unit, onCancel: (ActiveDownload) -> Unit,
onUpload: (DownloadedFile) -> Unit, onUpload: (DownloadedFile) -> Unit,
onCancelUpload: (ActiveDownload) -> Unit, onCancelUpload: (ActiveDownload) -> Unit,
onGenerateTestFile: () -> Unit,
onRefresh: () -> Unit, onRefresh: () -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
@@ -83,6 +84,15 @@ fun DownloadsScreen(
Text("Downloads", style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.primary) Text("Downloads", style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.primary)
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Text("${files.size} files · Movies/yaris-obd/", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant) Text("${files.size} files · Movies/yaris-obd/", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
Spacer(Modifier.height(10.dp))
Button(
onClick = onGenerateTestFile,
modifier = Modifier.height(34.dp),
shape = RoundedCornerShape(8.dp),
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 0.dp)
) {
Text("Create 120 MB test file", style = MaterialTheme.typography.bodySmall)
}
} }
} }
@@ -135,6 +135,7 @@ fun YarisMainScreen(client: OkHttpClient) {
onCancel = { dashcamVm.cancelDownload(it) }, onCancel = { dashcamVm.cancelDownload(it) },
onUpload = { dashcamVm.uploadFile(it) }, onUpload = { dashcamVm.uploadFile(it) },
onCancelUpload = { dashcamVm.cancelUpload(it) }, onCancelUpload = { dashcamVm.cancelUpload(it) },
onGenerateTestFile = { dashcamVm.generateTestFile() },
onRefresh = { dashcamVm.scanDownloads() }, onRefresh = { dashcamVm.scanDownloads() },
) )
2 -> SettingsScreen(client = client) 2 -> SettingsScreen(client = client)