Skip to content

feat(doc-scan): Query for FairScan, if built-in scanning not available#16427

Merged
alperozturk96 merged 4 commits intomasterfrom
ph/document_scanning_external
Mar 3, 2026
Merged

feat(doc-scan): Query for FairScan, if built-in scanning not available#16427
alperozturk96 merged 4 commits intomasterfrom
ph/document_scanning_external

Conversation

@PhilLab
Copy link
Contributor

@PhilLab PhilLab commented Feb 1, 2026

  • Tests written, or not not needed

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.

This approach was selected based on the discussion here: #12624 (comment)

Result

Screen_recording_20260201_143916.webm
  1. I thought about giving this menu entry a dedicated title, e.g. "Scan document from FairScan", but I decided against it - it's just more code to maintain and at the same time doesn't provide value to the user. Also it is not scalable: what if we add fallbacks to other scanner apps as well? So let's stick to the standard menu entry text.
  2. I also had in mind to make this menu entry an "Install FairScan" button, in case there is no appscan and FairScan also is not installed. But that felt too intrusive.

Behind the scenes

  1. To simplify the detection of the appscan project, a reflection-based approach was implemented. See the messages of the first two commits for why I am proposing this. If you don't agree with this approach, I can remove the commits from the PR, they don't impact the feature itself
  2. FairScan delivers a short-lived URI which we then use to upload the file. Originally, the filename is an "ugly" unix timestamp - you can also see this in above recording, from a prior attempt: 1769952395766.pdf.
  3. Notably, FairScan's Intent feature is still experimental (see their README) but I'd still propose to include it, because:
    • For Non-F-Droid users, all stays the same and uses the built-in scanner
    • For F-Droid users, the menu entry will only appear if FairScan is installed, so the area of impact is quite small.
    • The intent integration is very basic so it is not very likely that FairScan's next iteration will change it in a way which is incompatible with this.
    • If it breaks by changing the intent URI - the button will simply not show anymore so we are not worse off than the status-quo.
    • If it breaks otherwise, then we are still not worse off than having no scanner in the F-Droid variant at all.

@PhilLab PhilLab changed the title If built-in scanning is not available, check for FairScan Use FairScan, if built-in document scanning is not available Feb 1, 2026
@PhilLab PhilLab changed the title Use FairScan, if built-in document scanning is not available feat(doc-scan): Query for FairScan, if built-in scanning not available Feb 2, 2026
@alperozturk96
Copy link
Collaborator

@tobiasKaminsky What do you think, could you share your opinion?

@github-actions
Copy link

Hello there,
Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.

We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.

Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6

Thank you for contributing to Nextcloud and we hope to hear from you soon!

(If you believe you should not receive this message, you can add yourself to the blocklist.)

@PhilLab PhilLab force-pushed the ph/document_scanning_external branch from e169423 to 25b3249 Compare March 1, 2026 18:31
@PhilLab
Copy link
Contributor Author

PhilLab commented Mar 1, 2026

@tobiasKaminsky @alperozturk96 can you let me know your thoughts about the implementation?
As I said, I can easily drop the reflection-based appscan check, if you think this is entirely the wrong direction. It has its pros and cons, as explained in the respective commit message.

@tobiasKaminsky
Copy link
Member

I checked again with Fdroid, if we could use tiny cv:
https://gitlab.com/fdroid/fdroiddata/-/issues/3822

But since this will not work / is too complicated, I am fine with this approach ✔

@alperozturk96 alperozturk96 force-pushed the ph/document_scanning_external branch from 25b3249 to 825f386 Compare March 2, 2026 08:58
}
}

/**
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove unnecessary comment since interface and what it does already clear.

assertFalse(feature.isAvailable)
assertEquals(AppScanOptionalFeature.Stub, feature)

// Verify that calling getScanContract on stub throws UnsupportedOperationException
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove comment logic already explains itself.

}

/**
* Dagger component for testing VariantModule in isolation
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove unnecessary comment since interface and what it does already clear.

* In this variant, app scan should not be available
*/
@Test
fun testAppScanOptionalFeatureAvailability() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rename function name to testAppScanWhenNotAvailableShouldReturnError and remove function comment.

* In this variant, app scan should be available
*/
@Test
fun testAppScanOptionalFeatureAvailability() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rename function name to testAppScanWhenAvailableShouldReturnContract and remove function comment.

assertTrue(feature.isAvailable)
assertNotEquals(AppScanOptionalFeature.Stub, feature)

// Verify that calling getScanContract returns without raising an exception
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the comment. Since function name will be changed and what it is doing clear.


val currentDir = getCurrentDir()
val remotePath = if (currentDir != null) currentDir.remotePath else OCFile.ROOT_PATH
var fileDisplayNameTransformer: androidx.core.util.Function<Uri, String?>? = null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove or simplify androidx.core.util.Function<Uri, String?>? variable type if possible.

private FloatingActionButton mFabMain;
public static boolean isMultipleFileSelectedForCopyOrMove = false;

private static final Intent mFairScanIntent = new Intent("org.fairscan.app.action.SCAN_TO_PDF");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mFairScanIntent please replace this variable name to scanIntent.

I suggest to avoid using m prefix for new variable names to match with rest of the code. Also no need to indicate specific package name in the variable since it's is just a scan intent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alperozturk96 renamed, though made it explicit in the new name that it is an external app. This way you cannot confuse it with any internal intent.

<intent>
<!-- Since Android 11 (API level 30), this is required to detect the availability of FairScan to use
for external document scanning. See https://developer.android.com/training/package-visibility -->
<action android:name="org.fairscan.app.action.SCAN_TO_PDF" />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the comment.

<action android:name="org.fairscan.app.action.SCAN_TO_PDF" /> this already explains to developer what it is doing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alperozturk96 I moved it up and adjusted a little to fit the other declarations

@PhilLab PhilLab force-pushed the ph/document_scanning_external branch from 825f386 to 6bfe459 Compare March 2, 2026 17:58
@PhilLab PhilLab requested a review from alperozturk96 March 2, 2026 18:00
@PhilLab
Copy link
Contributor Author

PhilLab commented Mar 2, 2026

@alperozturk96 thanks for the helpful review, I force-pushed an update just now.

PhilLab added 4 commits March 3, 2026 09:52
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 <vcs@hasper.info>
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 <vcs@hasper.info>
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 <vcs@hasper.info>
Before, the filename from FairScan just was <unixTimestamp>.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 <vcs@hasper.info>
@alperozturk96 alperozturk96 force-pushed the ph/document_scanning_external branch from 6bfe459 to 94d5e7a Compare March 3, 2026 08:52
@alperozturk96 alperozturk96 merged commit 2da4633 into master Mar 3, 2026
17 of 20 checks passed
@alperozturk96 alperozturk96 deleted the ph/document_scanning_external branch March 3, 2026 09:00
@github-actions
Copy link

github-actions bot commented Mar 3, 2026

blue-Light-Screenshot test failed, but no output was generated. Maybe a preliminary stage failed.

@github-actions
Copy link

github-actions bot commented Mar 3, 2026

Codacy

SpotBugs

CategoryBaseNew
Bad practice4242
Correctness7575
Dodgy code253253
Experimental11
Internationalization77
Malicious code vulnerability22
Multithreaded correctness3434
Performance4343
Security1818
Total475475

@github-actions
Copy link

github-actions bot commented Mar 3, 2026

APK file: https://www.kaminsky.me/nc-dev/android-artifacts/16427.apk

qrcode

To test this change/fix you can simply download above APK file and install and test it in parallel to your existing Nextcloud app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants