CloudBridge

A cloud file manager for Android, powered by rclone.

How CloudBridge works — the full technical tour

This page documents the complete CloudBridge system: the app architecture, how rclone is embedded, the build pipeline, automatic releases, the update mechanism, and the Google Play publishing flow. It is meant for contributors and for anyone who wants to understand what is running on their device.

1. Project architecture

The repository contains three Gradle modules:

ModuleLanguagePurpose
app Java + Kotlin The Android application: UI, services, background tasks. Older code lives in the ca.pkay.rcloneexplorer namespace (inherited from rcloneExplorer), newer code in de.schuelken.cloudbridge.
rclone Go Cross-compiles rclone into librclone.so for every Android ABI. No rclone binary is executed; the library is loaded via JNI.
safdav Java Bridges Android's Storage Access Framework (SAF) to WebDAV, so rclone can read and write SD cards, USB sticks and other SAF-provided storage.

2. How rclone is embedded

rclone is a Go program. The rclone module builds it with the official Go NDK toolchain as a shared library (librclone.so) that exposes the rclone remote-control API over a C interface. The app calls into this library through JNI and exchanges JSON requests and responses — for example to list a directory, start a sync, or create a remote.

3. Build system

Prerequisites: JDK 17, Go 1.25+, Android SDK with the NDK version pinned in gradle.properties.

./gradlew assembleOssDebug     # debug APK
./gradlew assembleOssRelease   # signed release APKs
./gradlew bundleOssRelease -PnoAbiSplits   # AAB for Google Play

4. Version numbers

5. CI/CD — how a push becomes a release

The workflow .github/workflows/android.yml runs on every push to master:

  1. Check out the full history (needed for the commit-count version code).
  2. Install the pinned JDK, Go and NDK versions from gradle.properties.
  3. Decode the release keystore from the encrypted repository secret CB_KEYSTORE_BASE64 into a temporary file. Passwords arrive as masked secrets. The keystore is never committed.
  4. Build all signed release APKs (assembleOssRelease) and the signed AAB (bundleOssRelease -PnoAbiSplits).
  5. Create a GitHub pre-release tagged v1.0.1-beta.<shortsha> with the five APKs and the .aab attached. Previous beta releases are kept.

Other workflows: lint.yml runs unit tests and lint on pull requests, dependencies.yml rebuilds when build.gradle changes, and translations.yml checks translated resources for profanity.

6. The update checker

If enabled, a WorkManager job runs about every 14 days and asks the GitHub API whether a newer release tag exists. When it finds one, it shows a notification only: "A new version might be available." Tapping it opens the release page in your browser, where you can read the changelog and download the APK yourself. There is also an "Ignore Update" action for this version.

CloudBridge deliberately does not download or install APKs on its own. In-app self-updating is forbidden by Google Play policy, and manual installation keeps you in control of what runs on your device. For the same reason the app requests no install permissions.

On installations that came from an app store (Play Store, F-Droid and similar), the update check disables itself automatically — the store keeps you up to date.

7. Storage Access Framework (SAF)

Android does not let normal apps write to SD cards or USB sticks directly. CloudBridge solves this with the safdav module: it exposes any SAF document tree (as granted by you via the system picker) through a local WebDAV server, and rclone can then use that WebDAV endpoint like any other remote. This is how sync tasks can include external storage.

8. Google Play pipeline

Releasing on Play works alongside GitHub releases, not instead of them:

  1. Artifact: Play requires an Android App Bundle (.aab), produced by CI on every push and attached to the beta release.
  2. Signing: the AAB is signed with the project's upload key. On first upload, Google enrolls the app in Play App Signing: Google holds the final signing key and re-signs everything it distributes. Keep the upload keystore and its passwords safe — every future update must be signed with it.
  3. Upload: the AAB from the GitHub release is uploaded in Play Console to a testing track first (internal testing reviews in minutes), then promoted to production.
  4. versionCode: every upload must have a higher code than the last. The CI scheme (commit count × 10) guarantees this automatically.
  5. Policies that matter for CloudBridge: the MANAGE_EXTERNAL_STORAGE permission requires a declaration form (allowed for file managers, since broad file access is the core feature), the Data safety form can state that no data is collected (see the privacy policy), and in-app self-updating is not allowed — which is why the update checker is notification-only.

9. Local development

git clone https://github.com/thies2005/CloudBridge
cd CloudBridge
./gradlew assembleOssDebug
# APK: app/build/outputs/apk/oss/debug/

10. Troubleshooting

SymptomCause / fix
First build takes ~30 minutesrclone is being downloaded and cross-compiled for four ABIs. Normal; subsequent builds are incremental.
AAB build fails: Multiple shrunk-resources filesAPK splits were enabled during a bundle build. Use bundleOssRelease -PnoAbiSplits.
NDK / Go version mismatchBoth are pinned in gradle.properties (ndkVersion, goVersion) — install exactly those.
Rclone behaves differently than upstreamThe project uses its own fork with Android-specific fixes (for example Internxt token renewal). Compare against rCloneRepoUrl/rCloneRef in gradle.properties.
Release APK only signed with debug keyNo keystore.properties and no CB_* environment variables found — see section 3.