From 4e3bf7486fcb663d66d5b0983f8273b1253a9859 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 af36c934194b..8aadd93edbd8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -419,6 +419,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 a819d58d799192b9a983b0eaf64482070e746afe 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 | 50 +++++++++++++++++++ .../nextcloud/client/di/VariantModuleTest.kt | 47 +++++++++++++++++ 3 files changed, 101 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 8aadd93edbd8..8fd56c295fd5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -236,6 +236,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 { @@ -436,6 +439,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..ed7d07d4db34 --- /dev/null +++ b/app/src/androidTestGeneric/java/com/nextcloud/client/di/VariantModuleTest.kt @@ -0,0 +1,50 @@ +/* + * 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() + } + + @Test + fun testAppScanWhenNotAvailableShouldReturnError() { + val feature = component.appScanOptionalFeature() + + assertFalse(feature.isAvailable) + assertEquals(AppScanOptionalFeature.Stub, feature) + + try { + feature.getScanContract() + throw AssertionError("Expected UnsupportedOperationException") + } catch (e: UnsupportedOperationException) { + assertTrue(e.message?.contains("not available") == true) + } + } + + @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..3ac80ebe9d6c --- /dev/null +++ b/app/src/androidTestGplay/java/com/nextcloud/client/di/VariantModuleTest.kt @@ -0,0 +1,47 @@ +/* + * 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() + } + + @Test + fun testAppScanWhenAvailableShouldReturnContract() { + val feature = component.appScanOptionalFeature() + + assertTrue(feature.isAvailable) + assertNotEquals(AppScanOptionalFeature.Stub, feature) + + assertNotNull(feature.getScanContract()) + } + + @Component(modules = [VariantModule::class]) + interface TestVariantComponent { + fun appScanOptionalFeature(): AppScanOptionalFeature + } +} From 0d0c5fe909f80f80150dad2b567837f62da3488b 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 | 1 + .../fragment/OCFileListBottomSheetActions.java | 10 ++++++++++ .../ui/fragment/OCFileListBottomSheetDialog.kt | 5 +++++ .../ui/fragment/OCFileListFragment.java | 18 ++++++++++++++++++ 5 files changed, 36 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 41631d269609..ee62b6ffe416 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 @@ -434,6 +434,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 510fcb26a49d..7401f14ac3a0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -74,6 +74,7 @@ + diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java index c28f1e9837f9..0343cb17a292 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java @@ -53,6 +53,16 @@ public interface OCFileListBottomSheetActions { */ void scanDocUpload(); + /** + * Offers scanning a document in a supported external app and then upload to the current folder. + */ + void scanDocUploadFromApp(); + + /** + * @return true, if a supported external app is available for {@link #scanDocUploadFromApp()} + */ + boolean isScanDocUploadFromAppAvailable(); + /** * open template selection for creator @link Creator */ diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt index d67689722a45..4691e383a2f2 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt @@ -204,6 +204,11 @@ class OCFileListBottomSheetDialog( actions.scanDocUpload() dismiss() } + } else if (actions.isScanDocUploadFromAppAvailable) { + menuScanDocUpload.setOnClickListener { + actions.scanDocUploadFromApp() + dismiss() + } } else { menuScanDocUpload.visibility = View.GONE } 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 151c2305fb38..7660abdacbc4 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 @@ -228,6 +228,8 @@ public class OCFileListFragment extends ExtendedListFragment implements private FloatingActionButton mFabMain; public static boolean isMultipleFileSelectedForCopyOrMove = false; + private static final Intent scanIntentExternalApp = new Intent("org.fairscan.app.action.SCAN_TO_PDF"); + @Inject DeviceInfo deviceInfo; protected enum MenuItemAddRemove { @@ -590,6 +592,22 @@ public void scanDocUpload() { } } + @Override + public void scanDocUploadFromApp() { + requireActivity().startActivityForResult( + scanIntentExternalApp, + FileDisplayActivity.REQUEST_CODE__SELECT_CONTENT_FROM_APPS); + } + + @Override + public boolean isScanDocUploadFromAppAvailable() { + var context = getActivity(); + if (context == null) { + return false; + } + return scanIntentExternalApp.resolveActivity(context.getPackageManager()) != null; + } + @Override public void uploadFiles() { if (!(getActivity() instanceof FileActivity fileActivity)) { From 94d5e7ac4b9bbd4a297b42df4f4e403fa5d0e8c4 Mon Sep 17 00:00:00 2001 From: Philipp Hasper 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 | 3 +- 2 files changed, 27 insertions(+), 5 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 2925b1ba3b62..21a74acb64bf 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 @@ -45,6 +45,7 @@ import android.view.inputmethod.InputMethodManager import androidx.activity.OnBackPressedCallback import androidx.annotation.VisibleForTesting import androidx.appcompat.widget.SearchView +import androidx.core.util.Function import androidx.core.view.MenuItemCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -157,10 +158,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 @@ -971,10 +974,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 && ( @@ -1108,7 +1114,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) { @@ -1130,6 +1136,17 @@ class FileDisplayActivity : val currentDir = getCurrentDir() val remotePath = if (currentDir != null) currentDir.remotePath else OCFile.ROOT_PATH + var fileDisplayNameTransformer: 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, @@ -1140,7 +1157,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() @@ -3105,6 +3123,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 7660abdacbc4..65ef422fa234 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 @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Philipp Hasper * SPDX-FileCopyrightText: 2023 TSI-mc * SPDX-FileCopyrightText: 2018-2023 Tobias Kaminsky * SPDX-FileCopyrightText: 2022 Álvaro Brey @@ -596,7 +597,7 @@ public void scanDocUpload() { public void scanDocUploadFromApp() { requireActivity().startActivityForResult( scanIntentExternalApp, - FileDisplayActivity.REQUEST_CODE__SELECT_CONTENT_FROM_APPS); + FileDisplayActivity.REQUEST_CODE__SELECT_CONTENT_FROM_APPS_AUTO_RENAME); } @Override