From d79f89eba0b6e546753539de94be1d115de22aa4 Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Tue, 6 Jan 2026 16:16:19 +0100 Subject: [PATCH 1/4] refactor(doc-scan): Simplify handling of optional appscan project To enable the document scanning feature for a specific build variant, previously two places had to be adjusted: - The build.gradle.kts, in the "region AppScan" - A variant-specific implementation for VariantModule.kt Now, only the first one is required and the VariantModule.kt handles it automatically. Benefits: Only a single place to change. And no code duplication Drawback: Reflection is a bit more brittle - it all depends on the package and class name to not change Signed-off-by: Philipp Hasper --- app/build.gradle.kts | 1 + .../com/nextcloud/client/di/VariantModule.kt | 20 --------- .../com/nextcloud/client/di/VariantModule.kt | 23 ---------- .../com/nextcloud/client/di/VariantModule.kt | 23 ---------- .../com/nextcloud/client/di/VariantModule.kt | 45 +++++++++++++++++++ .../com/nextcloud/client/di/VariantModule.kt | 23 ---------- .../com/nextcloud/client/di/VariantModule.kt | 20 --------- .../com/nextcloud/appscan/ScanPageContract.kt | 3 +- 8 files changed, 48 insertions(+), 110 deletions(-) delete mode 100644 app/src/generic/java/com/nextcloud/client/di/VariantModule.kt delete mode 100644 app/src/gplay/java/com/nextcloud/client/di/VariantModule.kt delete mode 100644 app/src/huawei/java/com/nextcloud/client/di/VariantModule.kt create mode 100644 app/src/main/java/com/nextcloud/client/di/VariantModule.kt delete mode 100644 app/src/qa/java/com/nextcloud/client/di/VariantModule.kt delete mode 100644 app/src/versionDev/java/com/nextcloud/client/di/VariantModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ef2466fd2d2b..a0d23b9fd37e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -422,6 +422,7 @@ dependencies { // endregion // region AppScan, document scanner not available on FDroid (generic) due to OpenCV binaries + // To enable the feature for another variant, add it here. "gplayImplementation"(project(":appscan")) "huaweiImplementation"(project(":appscan")) "qaImplementation"(project(":appscan")) diff --git a/app/src/generic/java/com/nextcloud/client/di/VariantModule.kt b/app/src/generic/java/com/nextcloud/client/di/VariantModule.kt deleted file mode 100644 index d73f39e243d3..000000000000 --- a/app/src/generic/java/com/nextcloud/client/di/VariantModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2023 Álvaro Brey - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ -package com.nextcloud.client.di - -import com.nextcloud.client.documentscan.AppScanOptionalFeature -import dagger.Module -import dagger.Provides -import dagger.Reusable - -@Module -internal class VariantModule { - @Provides - @Reusable - fun scanOptionalFeature(): AppScanOptionalFeature = AppScanOptionalFeature.Stub -} diff --git a/app/src/gplay/java/com/nextcloud/client/di/VariantModule.kt b/app/src/gplay/java/com/nextcloud/client/di/VariantModule.kt deleted file mode 100644 index 627cb92a06ef..000000000000 --- a/app/src/gplay/java/com/nextcloud/client/di/VariantModule.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2023 Álvaro Brey - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ -package com.nextcloud.client.di - -import com.nextcloud.appscan.ScanPageContract -import com.nextcloud.client.documentscan.AppScanOptionalFeature -import dagger.Module -import dagger.Provides -import dagger.Reusable - -@Module -internal class VariantModule { - @Provides - @Reusable - fun scanOptionalFeature(): AppScanOptionalFeature = object : AppScanOptionalFeature() { - override fun getScanContract() = ScanPageContract() - } -} diff --git a/app/src/huawei/java/com/nextcloud/client/di/VariantModule.kt b/app/src/huawei/java/com/nextcloud/client/di/VariantModule.kt deleted file mode 100644 index 627cb92a06ef..000000000000 --- a/app/src/huawei/java/com/nextcloud/client/di/VariantModule.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2023 Álvaro Brey - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ -package com.nextcloud.client.di - -import com.nextcloud.appscan.ScanPageContract -import com.nextcloud.client.documentscan.AppScanOptionalFeature -import dagger.Module -import dagger.Provides -import dagger.Reusable - -@Module -internal class VariantModule { - @Provides - @Reusable - fun scanOptionalFeature(): AppScanOptionalFeature = object : AppScanOptionalFeature() { - override fun getScanContract() = ScanPageContract() - } -} diff --git a/app/src/main/java/com/nextcloud/client/di/VariantModule.kt b/app/src/main/java/com/nextcloud/client/di/VariantModule.kt new file mode 100644 index 000000000000..78899be6aec7 --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/di/VariantModule.kt @@ -0,0 +1,45 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Philipp Hasper + * SPDX-FileCopyrightText: 2023 Álvaro Brey + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ +package com.nextcloud.client.di + +import androidx.activity.result.contract.ActivityResultContract +import com.nextcloud.client.documentscan.AppScanOptionalFeature +import dagger.Module +import dagger.Provides +import dagger.Reusable + +@Module +internal class VariantModule { + /** + * Using reflection to determine whether the ScanPageContract class from the appscan project is available. + * If yes, an instance of it is returned. If not, a stub is returned indicating the feature is not available. + * + * To make it available for your specific variant, make sure it is included in your build.gradle, + * e.g.: `"qaImplementation"(project(":appscan"))` + */ + @Provides + @Reusable + fun scanOptionalFeature(): AppScanOptionalFeature = try { + // Try to load the ScanPageContract class only if the appscan project is present + val clazz = Class.forName("com.nextcloud.appscan.ScanPageContract") + + @Suppress("UNCHECKED_CAST") + val contractInstance = + clazz.getDeclaredConstructor().newInstance() as ActivityResultContract + object : AppScanOptionalFeature() { + override fun getScanContract(): ActivityResultContract = contractInstance + } + } catch (_: ClassNotFoundException) { + // appscan module is not present in this variant + AppScanOptionalFeature.Stub + } catch (_: Exception) { + // Any reflection/instantiation error -> be safe and use stub + AppScanOptionalFeature.Stub + } +} diff --git a/app/src/qa/java/com/nextcloud/client/di/VariantModule.kt b/app/src/qa/java/com/nextcloud/client/di/VariantModule.kt deleted file mode 100644 index 627cb92a06ef..000000000000 --- a/app/src/qa/java/com/nextcloud/client/di/VariantModule.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2023 Álvaro Brey - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ -package com.nextcloud.client.di - -import com.nextcloud.appscan.ScanPageContract -import com.nextcloud.client.documentscan.AppScanOptionalFeature -import dagger.Module -import dagger.Provides -import dagger.Reusable - -@Module -internal class VariantModule { - @Provides - @Reusable - fun scanOptionalFeature(): AppScanOptionalFeature = object : AppScanOptionalFeature() { - override fun getScanContract() = ScanPageContract() - } -} diff --git a/app/src/versionDev/java/com/nextcloud/client/di/VariantModule.kt b/app/src/versionDev/java/com/nextcloud/client/di/VariantModule.kt deleted file mode 100644 index d73f39e243d3..000000000000 --- a/app/src/versionDev/java/com/nextcloud/client/di/VariantModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2023 Álvaro Brey - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ -package com.nextcloud.client.di - -import com.nextcloud.client.documentscan.AppScanOptionalFeature -import dagger.Module -import dagger.Provides -import dagger.Reusable - -@Module -internal class VariantModule { - @Provides - @Reusable - fun scanOptionalFeature(): AppScanOptionalFeature = AppScanOptionalFeature.Stub -} diff --git a/appscan/src/main/java/com/nextcloud/appscan/ScanPageContract.kt b/appscan/src/main/java/com/nextcloud/appscan/ScanPageContract.kt index 65a30f79500e..2170bc0be9b6 100644 --- a/appscan/src/main/java/com/nextcloud/appscan/ScanPageContract.kt +++ b/appscan/src/main/java/com/nextcloud/appscan/ScanPageContract.kt @@ -5,13 +5,14 @@ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only */ -package com.nextcloud.appscan +package com.nextcloud.appscan // Note: if class package or name changes, you must adjust the app's VariantModule.kt import android.app.Activity import android.content.Context import android.content.Intent import androidx.activity.result.contract.ActivityResultContract +@Suppress("unused") // Class is instantiated via reflection class ScanPageContract : ActivityResultContract() { override fun createIntent(context: Context, input: Unit): Intent { return Intent(context, AppScanActivity::class.java) From 35db5a83432159711fd82160d095ee44602fb65c Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Sun, 1 Feb 2026 09:05:34 +0100 Subject: [PATCH 2/4] test(doc-scan) Add test for reflection-based appscan check Testing two main variants, one without appscan, and one with it. However, as of now the automated test for the gplay flavor does not run automatically in the CI, as we only test the generic flavor. So the test's purpose was just to verify the reflection approach locally. Signed-off-by: Philipp Hasper --- app/build.gradle.kts | 4 ++ .../nextcloud/client/di/VariantModuleTest.kt | 57 +++++++++++++++++++ .../nextcloud/client/di/VariantModuleTest.kt | 54 ++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 app/src/androidTestGeneric/java/com/nextcloud/client/di/VariantModuleTest.kt create mode 100644 app/src/androidTestGplay/java/com/nextcloud/client/di/VariantModuleTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a0d23b9fd37e..96b4bb2c8557 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -240,6 +240,9 @@ kapt.useBuildCache = true ksp.arg("room.schemaLocation", "$projectDir/schemas") +// Configure KSP for test variants +ksp.arg("dagger.moduleName", project.name) + kotlin.compilerOptions.jvmTarget.set(JvmTarget.JVM_21) spotless.kotlin { @@ -439,6 +442,7 @@ dependencies { implementation(libs.dagger.android.support) ksp(libs.dagger.compiler) ksp(libs.dagger.processor) + kspAndroidTest(libs.dagger.compiler) // endregion // region Crypto diff --git a/app/src/androidTestGeneric/java/com/nextcloud/client/di/VariantModuleTest.kt b/app/src/androidTestGeneric/java/com/nextcloud/client/di/VariantModuleTest.kt new file mode 100644 index 000000000000..735fc3cef497 --- /dev/null +++ b/app/src/androidTestGeneric/java/com/nextcloud/client/di/VariantModuleTest.kt @@ -0,0 +1,57 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.client.di + +import com.nextcloud.client.documentscan.AppScanOptionalFeature +import dagger.Component +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +/** + * Unit test for [VariantModule] that tests the reflection-based approach + * to conditionally load the ScanPageContract. + */ +class VariantModuleTest { + + private lateinit var component: TestVariantComponent + + @Before + fun setup() { + component = DaggerVariantModuleTest_TestVariantComponent.create() + } + + /** + * In this variant, app scan should not be available + */ + @Test + fun testAppScanOptionalFeatureAvailability() { + val feature = component.appScanOptionalFeature() + + assertFalse(feature.isAvailable) + assertEquals(AppScanOptionalFeature.Stub, feature) + + // Verify that calling getScanContract on stub throws UnsupportedOperationException + try { + feature.getScanContract() + throw AssertionError("Expected UnsupportedOperationException") + } catch (e: UnsupportedOperationException) { + assertTrue(e.message?.contains("not available") == true) + } + } + + /** + * Dagger component for testing VariantModule in isolation + */ + @Component(modules = [VariantModule::class]) + interface TestVariantComponent { + fun appScanOptionalFeature(): AppScanOptionalFeature + } +} diff --git a/app/src/androidTestGplay/java/com/nextcloud/client/di/VariantModuleTest.kt b/app/src/androidTestGplay/java/com/nextcloud/client/di/VariantModuleTest.kt new file mode 100644 index 000000000000..51bd43b65377 --- /dev/null +++ b/app/src/androidTestGplay/java/com/nextcloud/client/di/VariantModuleTest.kt @@ -0,0 +1,54 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.client.di + +import com.nextcloud.client.documentscan.AppScanOptionalFeature +import dagger.Component +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +/** + * Unit test for [VariantModule] that tests the reflection-based approach + * to conditionally load the ScanPageContract. + */ +class VariantModuleTest { + + private lateinit var component: TestVariantComponent + + @Before + fun setup() { + component = DaggerVariantModuleTest_TestVariantComponent.create() + } + + /** + * In this variant, app scan should be available + */ + @Test + fun testAppScanOptionalFeatureAvailability() { + val feature = component.appScanOptionalFeature() + + assertTrue(feature.isAvailable) + assertNotEquals(AppScanOptionalFeature.Stub, feature) + + // Verify that calling getScanContract returns without raising an exception + assertNotNull(feature.getScanContract()) + } + + /** + * Dagger component for testing VariantModule in isolation + */ + @Component(modules = [VariantModule::class]) + interface TestVariantComponent { + fun appScanOptionalFeature(): AppScanOptionalFeature + } +} From acf7603a101bab763a534e0904f533e894f0f6da Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Sat, 31 Jan 2026 22:06:13 +0100 Subject: [PATCH 3/4] feat(doc-scan): If built-in scanning not available, check for FairScan Built-in scanning is not available for all build variants - e.g. not for "generic", which is the flavor for the F-Droid release. It doesn't allow the non-reproducible TinyOpenCV build. If this is the case, we are checking whether the open source scanning app FairScan is available (https://github.com/pynicolas/FairScan) and open that one for scanning. The declaration in the AndroidManifest is required since Android 11 (API level 30), otherwise the Intent would always be null. See https://developer.android.com/training/package-visibility Signed-off-by: Philipp Hasper --- .../android/ui/dialog/DialogFragmentIT.kt | 2 ++ app/src/main/AndroidManifest.xml | 5 +++++ .../fragment/OCFileListBottomSheetActions.java | 10 ++++++++++ .../ui/fragment/OCFileListBottomSheetDialog.kt | 5 +++++ .../ui/fragment/OCFileListFragment.java | 18 ++++++++++++++++++ 5 files changed, 40 insertions(+) diff --git a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt index 4f039da6bf63..31b00cb1d8b6 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt @@ -438,6 +438,8 @@ class DialogFragmentIT : AbstractIT() { override fun newPresentation() = Unit override fun directCameraUpload() = Unit override fun scanDocUpload() = Unit + override fun scanDocUploadFromApp() = Unit + override fun isScanDocUploadFromAppAvailable(): Boolean = false override fun showTemplate(creator: Creator?, headline: String?) = Unit override fun createRichWorkspace() = Unit } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ee6e7fd2bc80..18e7a7506570 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -107,6 +107,11 @@ + + + + Date: Sun, 1 Feb 2026 14:30:48 +0100 Subject: [PATCH 4/4] feat(doc-scan): Rename file from external scanner Before, the filename from FairScan just was .pdf. Now, we are giving it our own timestamped name, just like we do for images and videos captured via the camera intent. This uses the same fileDisplayNameTransformer as PR #16298 Signed-off-by: Philipp Hasper --- .../ui/activity/FileDisplayActivity.kt | 29 ++++++++++++++++--- .../ui/fragment/OCFileListFragment.java | 2 +- .../android/ui/helpers/UriUploader.kt | 10 +++++-- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 79925570bbac..4ea8c10dbf7a 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -44,6 +44,7 @@ import android.view.inputmethod.InputMethodManager import androidx.activity.OnBackPressedCallback import androidx.annotation.VisibleForTesting import androidx.appcompat.widget.SearchView +import androidx.core.content.FileProvider import androidx.core.view.MenuItemCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -155,10 +156,12 @@ import com.owncloud.android.utils.PermissionUtil.requestNotificationPermission import com.owncloud.android.utils.PermissionUtil.requestStoragePermissionIfNeeded import com.owncloud.android.utils.PushUtils import com.owncloud.android.utils.StringUtils +import com.owncloud.android.utils.UriUtils import com.owncloud.android.utils.theme.CapabilityUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.apache.commons.io.FilenameUtils import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -970,10 +973,13 @@ class FileDisplayActivity : */ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (data != null && - requestCode == REQUEST_CODE__SELECT_CONTENT_FROM_APPS && + ( + requestCode == REQUEST_CODE__SELECT_CONTENT_FROM_APPS || + requestCode == REQUEST_CODE__SELECT_CONTENT_FROM_APPS_AUTO_RENAME + ) && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) ) { - requestUploadOfContentFromApps(data, resultCode) + requestUploadOfContentFromApps(requestCode, resultCode, data) } else if (data != null && requestCode == REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM && ( @@ -1107,7 +1113,7 @@ class FileDisplayActivity : } } - private fun requestUploadOfContentFromApps(contentIntent: Intent, resultCode: Int) { + private fun requestUploadOfContentFromApps(requestCode: Int, resultCode: Int, contentIntent: Intent) { val streamsToUpload = ArrayList() if (contentIntent.clipData != null && (contentIntent.clipData?.itemCount ?: 0) > 0) { @@ -1129,6 +1135,17 @@ class FileDisplayActivity : val currentDir = getCurrentDir() val remotePath = if (currentDir != null) currentDir.remotePath else OCFile.ROOT_PATH + var fileDisplayNameTransformer: androidx.core.util.Function? = null + if (requestCode == REQUEST_CODE__SELECT_CONTENT_FROM_APPS_AUTO_RENAME) { + fileDisplayNameTransformer = { uri: Uri -> + val displayName = UriUtils.getDisplayNameForUri(uri, applicationContext) + if (displayName != null && displayName.isNotEmpty()) { + FileOperationsHelper.getTimestampedFileName("." + FilenameUtils.getExtension(displayName)) + } else { + null + } + } + } val uploader = UriUploader( this, @@ -1139,7 +1156,8 @@ class FileDisplayActivity : ), behaviour, false, // Not show waiting dialog while file is being copied from private storage - null // Not needed copy temp task listener + null, // Not needed copy temp task listener + fileDisplayNameTransformer ) uploader.uploadUris() @@ -3139,6 +3157,9 @@ class FileDisplayActivity : @JvmField val REQUEST_CODE__UPLOAD_FROM_VIDEO_CAMERA: Int = REQUEST_CODE__LAST_SHARED + 6 + @JvmField + val REQUEST_CODE__SELECT_CONTENT_FROM_APPS_AUTO_RENAME: Int = REQUEST_CODE__LAST_SHARED + 7 + protected val DELAY_TO_REQUEST_REFRESH_OPERATION_LATER: Long = DELAY_TO_REQUEST_OPERATIONS_LATER + 350 private val TAG: String = FileDisplayActivity::class.java.getSimpleName() diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 3a45a21e31bc..33e33b2b37a6 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -584,7 +584,7 @@ public void scanDocUpload() { public void scanDocUploadFromApp() { requireActivity().startActivityForResult( mFairScanIntent, - FileDisplayActivity.REQUEST_CODE__SELECT_CONTENT_FROM_APPS); + FileDisplayActivity.REQUEST_CODE__SELECT_CONTENT_FROM_APPS_AUTO_RENAME); } @Override diff --git a/app/src/main/java/com/owncloud/android/ui/helpers/UriUploader.kt b/app/src/main/java/com/owncloud/android/ui/helpers/UriUploader.kt index 9f08ad99bbce..6b4c63cd71d2 100644 --- a/app/src/main/java/com/owncloud/android/ui/helpers/UriUploader.kt +++ b/app/src/main/java/com/owncloud/android/ui/helpers/UriUploader.kt @@ -13,6 +13,7 @@ package com.owncloud.android.ui.helpers import android.content.ContentResolver import android.net.Uri import android.os.Parcelable +import androidx.core.util.Function import com.nextcloud.client.account.User import com.nextcloud.client.jobs.upload.FileUploadHelper import com.owncloud.android.R @@ -42,14 +43,16 @@ import com.owncloud.android.utils.UriUtils.getDisplayNameForUri "Detekt.SpreadOperator", "Detekt.TooGenericExceptionCaught" ) // legacy code -class UriUploader( +class UriUploader @JvmOverloads constructor( private val mActivity: FileActivity, private val mUrisToUpload: List, private val mUploadPath: String, private val user: User, private val mBehaviour: Int, private val mShowWaitingDialog: Boolean, - private val mCopyTmpTaskListener: OnCopyTmpFilesTaskListener? + private val mCopyTmpTaskListener: OnCopyTmpFilesTaskListener?, + /** If non-null, this function is called to determine the desired display name (i.e. filename) after upload**/ + private val mFileDisplayNameTransformer: Function? = null ) { enum class UriUploaderResultCode { @@ -113,7 +116,8 @@ class UriUploader( } private fun getRemotePathForUri(sourceUri: Uri): String { - val displayName = getDisplayNameForUri(sourceUri, mActivity) + val displayName = mFileDisplayNameTransformer?.apply(sourceUri) + ?: getDisplayNameForUri(sourceUri, mActivity) require(displayName != null) { "Display name cannot be null" } return mUploadPath + displayName }