From 84e12b50779d7f070bdb5ec93aab67a5545aeff4 Mon Sep 17 00:00:00 2001 From: JosipK Date: Sat, 18 Apr 2026 21:42:46 +0200 Subject: [PATCH 1/3] fix oom issue when handling files and media --- .../com/qlarr/app/ui/survey/QlarrWebView.kt | 92 ++++++++++--------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/com/qlarr/app/ui/survey/QlarrWebView.kt b/app/src/main/java/com/qlarr/app/ui/survey/QlarrWebView.kt index 3a7fcf8..1853210 100644 --- a/app/src/main/java/com/qlarr/app/ui/survey/QlarrWebView.kt +++ b/app/src/main/java/com/qlarr/app/ui/survey/QlarrWebView.kt @@ -313,19 +313,22 @@ constructor( key: String, fileName: String, ) { - context.contentResolver.openInputStream(saverUri!!)?.let { - it.use { stream -> - val uploadFile = - emNavProcessor.uploadFile( - key, - fileName, - stream.readBytes(), - ) - val string = objectMapper.writeValueAsString(uploadFile) - resetFileUploadVariables() - loadUrlOnUiThread("javascript:onFileUploaded($string)") + val uri = saverUri ?: return + val uuid = UUID.randomUUID().toString() + val dest = FileUtils.getResponseFile(context, uuid, survey.id, responseId!!) + context.contentResolver.openInputStream(uri)?.use { input -> + dest.outputStream().use { input.copyTo(it) } } - } + val uploadFile = + emNavProcessor.saveFileResponse( + fileName = fileName, + storedFilename = uuid, + fileSize = dest.length(), + key = key, + ) + val string = objectMapper.writeValueAsString(uploadFile) + resetFileUploadVariables() + loadUrlOnUiThread("javascript:onFileUploaded($string)") } @JavascriptInterface @@ -412,14 +415,15 @@ constructor( emNavProcessor = EMNavProcessor(context, survey) { loadDataWithBaseURL(CUSTOM_DOMAIN, data, null, null, null) - } - } + } + } - fun onCameraResult() { - context.contentResolver.openInputStream(saverUri!!)?.use { stream -> - val size = stream.readBytes().size.toLong() + fun onCameraResult() { + val uri = saverUri ?: return + val uuid = uri.toString().substringAfterLast("/").substringBefore(".") + val file = FileUtils.getResponseFile(context, uuid, survey.id, responseId!!) + val size = file.length() val shouldCompress = isSizeViolated(size, true) - val uuid = saverUri.toString().substringAfterLast("/").substringBefore(".") val key = operationKey!! CoroutineScope(Dispatchers.IO).launch { val result = @@ -431,15 +435,7 @@ constructor( ) val finalSize = if (shouldCompress) { - compress( - FileUtils.getResponseFile( - context, - uuid.toString(), - survey.id, - responseId!!, - ), - maxSizeKb!! * 1024L, - ) + compress(file, maxSizeKb!! * 1024L) } else { result.size } @@ -451,7 +447,6 @@ constructor( })", ) resetFileUploadVariables() - } } } @@ -470,31 +465,34 @@ constructor( fun onBarcodeScanned(contents: String) { loadUrlOnUiThread("javascript:onBarcodeScanned$operationKey(\"$contents\")") - } - - private fun loadUrlOnUiThread(url: String) { - (context as Activity).runOnUiThread { - loadUrl(url) } - } - fun onVideoResult(contentUri: Uri?) { - val stream = context.contentResolver.openInputStream(contentUri!!) + private fun loadUrlOnUiThread(url: String) { + (context as Activity).runOnUiThread { + loadUrl(url) + } + } - stream?.use { - val byteArray = stream.readBytes() - val size = byteArray.size.toLong() - if (isSizeViolated(size)) { + fun onVideoResult(contentUri: Uri?) { + val uri = contentUri ?: return + val querySize = queryContentSize(uri) + if (querySize != null && isSizeViolated(querySize)) { resetFileUploadVariables() return } val key = operationKey!! + val uuid = UUID.randomUUID().toString() CoroutineScope(Dispatchers.IO).launch { + val dest = FileUtils.getResponseFile(context, uuid, survey.id, responseId!!) + context.contentResolver.openInputStream(uri)?.use { input -> + dest.outputStream().use { input.copyTo(it) } + } val result = - emNavProcessor.uploadFile( - key = key, + emNavProcessor.saveFileResponse( fileName = "captured-video.mp4", - byteArray = byteArray, + storedFilename = uuid, + fileSize = dest.length(), + key = key, ) loadUrlOnUiThread( "javascript:onVideoCaptured$key(${ @@ -506,7 +504,13 @@ constructor( resetFileUploadVariables() } } - } + + private fun queryContentSize(uri: Uri): Long? = + context.contentResolver.query(uri, null, null, null, null)?.use { cursor -> + if (!cursor.moveToFirst()) return@use null + val idx = cursor.getColumnIndex(OpenableColumns.SIZE) + if (idx == -1 || cursor.isNull(idx)) null else cursor.getLong(idx) + } private fun isSizeViolated( size: Long, From 5178e75685fba3f5ff2eea494b7a861e7df327cd Mon Sep 17 00:00:00 2001 From: JosipK Date: Sat, 18 Apr 2026 22:47:10 +0200 Subject: [PATCH 2/3] cleanup --- .../com/qlarr/app/ui/survey/EMNavProcessor.kt | 22 ------------------- .../com/qlarr/app/ui/survey/QlarrWebView.kt | 4 +++- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/qlarr/app/ui/survey/EMNavProcessor.kt b/app/src/main/java/com/qlarr/app/ui/survey/EMNavProcessor.kt index 382fd82..3743fbe 100644 --- a/app/src/main/java/com/qlarr/app/ui/survey/EMNavProcessor.kt +++ b/app/src/main/java/com/qlarr/app/ui/survey/EMNavProcessor.kt @@ -394,28 +394,6 @@ class EMNavProcessor( ) } - fun uploadFile( - key: String, - fileName: String, - byteArray: ByteArray, - ): ResponseUploadFile { - val uuid = UUID.randomUUID().toString() - val responseFile = - FileUtils.getResponseFile( - context = getActivity(), - fileName = uuid, - surveyId = survey.id, - responseId = responseId.toString(), - ) - responseFile.writeBytes(byteArray) - return saveFileResponse( - fileName = fileName, - storedFilename = uuid, - key = key, - fileSize = responseFile.length(), - ) - } - fun saveFileResponse( fileName: String, storedFilename: String, diff --git a/app/src/main/java/com/qlarr/app/ui/survey/QlarrWebView.kt b/app/src/main/java/com/qlarr/app/ui/survey/QlarrWebView.kt index 1853210..c8cdd20 100644 --- a/app/src/main/java/com/qlarr/app/ui/survey/QlarrWebView.kt +++ b/app/src/main/java/com/qlarr/app/ui/survey/QlarrWebView.kt @@ -315,7 +315,8 @@ constructor( ) { val uri = saverUri ?: return val uuid = UUID.randomUUID().toString() - val dest = FileUtils.getResponseFile(context, uuid, survey.id, responseId!!) + CoroutineScope(Dispatchers.IO).launch { + val dest = FileUtils.getResponseFile(context, uuid, survey.id, responseId!!) context.contentResolver.openInputStream(uri)?.use { input -> dest.outputStream().use { input.copyTo(it) } } @@ -329,6 +330,7 @@ constructor( val string = objectMapper.writeValueAsString(uploadFile) resetFileUploadVariables() loadUrlOnUiThread("javascript:onFileUploaded($string)") + } } @JavascriptInterface From 88141c10fe4362b102a46bb4145259a38ebca373 Mon Sep 17 00:00:00 2001 From: JosipK Date: Sat, 18 Apr 2026 23:17:15 +0200 Subject: [PATCH 3/3] additional size check after copy --- .../com/qlarr/app/ui/survey/QlarrWebView.kt | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/qlarr/app/ui/survey/QlarrWebView.kt b/app/src/main/java/com/qlarr/app/ui/survey/QlarrWebView.kt index c8cdd20..4ead419 100644 --- a/app/src/main/java/com/qlarr/app/ui/survey/QlarrWebView.kt +++ b/app/src/main/java/com/qlarr/app/ui/survey/QlarrWebView.kt @@ -478,22 +478,36 @@ constructor( fun onVideoResult(contentUri: Uri?) { val uri = contentUri ?: return val querySize = queryContentSize(uri) - if (querySize != null && isSizeViolated(querySize)) { + if (querySize != null && isSizeViolated(querySize)) { resetFileUploadVariables() return } val key = operationKey!! val uuid = UUID.randomUUID().toString() + val maxKb = maxSizeKb CoroutineScope(Dispatchers.IO).launch { val dest = FileUtils.getResponseFile(context, uuid, survey.id, responseId!!) context.contentResolver.openInputStream(uri)?.use { input -> dest.outputStream().use { input.copyTo(it) } } + val actualSize = dest.length() + if (maxKb != null && actualSize / 1024 > maxKb) { + dest.delete() + (context as Activity).runOnUiThread { + surveyActivity?.showMaxSizeValidation( + (actualSize / 1024).toInt(), + maxKb, + false, + ) + } + resetFileUploadVariables() + return@launch + } val result = emNavProcessor.saveFileResponse( fileName = "captured-video.mp4", storedFilename = uuid, - fileSize = dest.length(), + fileSize = actualSize, key = key, ) loadUrlOnUiThread(