From 2f2b9ab4108c071aa85749eeb27af7827e30bd09 Mon Sep 17 00:00:00 2001 From: MaxDac <25101798+MaxDac@users.noreply.github.com> Date: Tue, 3 Nov 2026 15:04:59 +0000 Subject: [PATCH 1/3] Remove INTERNET and ACCESS_NETWORK_STATE from the libre APK Strip both permissions in :app only (MarkNotes Plus keeps them via :app-shared) and skip the network callback when no provider exists, so launch cannot throw a SecurityException even if 'Sync when reconnected' is still on. Co-Authored-By: Claude Sonnet 5.5 --- AGENTS.md | 2 ++ .../github/maxdac/marknotes/MainActivity.kt | 6 +++++- .../maxdac/marknotes/sync/SyncManager.kt | 3 +++ .../marknotes/ReconnectSettingsFlowTest.kt | 21 +++++++++++++++++++ app/src/main/AndroidManifest.xml | 7 ++++++- docs/fdroid.md | 9 ++++---- fdroid/inclusion-mr.md | 4 ++-- fdroid/review-handover.md | 4 ++-- scripts/check-release-apk.py | 2 -- 9 files changed, 46 insertions(+), 12 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 25f0a8a..a077822 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,6 +35,8 @@ and `MarkNotesApp`. So: - changing a public API of `:core` or `:app-shared` (`DriveConnector`, `MarkNotesApp`, `AppContainer`, ...) breaks MarkNotes Pro until it bumps the submodule and adapts: say so in the PR and follow up there; - dependencies both apps use go in `gradle/libs.versions.toml` here; MarkNotes Pro reads this catalog as `libs`. +- `:app` strips `INTERNET` and `ACCESS_NETWORK_STATE` from this offline edition; code in `:app-shared` that needs the + network must be gated on a provider existing (see `SyncManager.canWatchNetwork`). ## Build and test diff --git a/app-shared/src/main/kotlin/io/github/maxdac/marknotes/MainActivity.kt b/app-shared/src/main/kotlin/io/github/maxdac/marknotes/MainActivity.kt index 17aadcc..6c4b904 100644 --- a/app-shared/src/main/kotlin/io/github/maxdac/marknotes/MainActivity.kt +++ b/app-shared/src/main/kotlin/io/github/maxdac/marknotes/MainActivity.kt @@ -166,8 +166,12 @@ class MainActivity : ComponentActivity() { * background syncs once the app is back in the foreground. Nothing syncs while the app is in the background. */ private fun watchNetwork() { + val syncManager = appContainer.syncManager + // Editions without a provider (libre) drop ACCESS_NETWORK_STATE, so registering would throw. This must not + // depend on the "Sync when reconnected" setting: it may still be on after an upgrade from 0.1.x. + if (!syncManager.canWatchNetwork) return val connectivity = getSystemService(ConnectivityManager::class.java) ?: return - val reconnect = appContainer.syncManager.reconnect + val reconnect = syncManager.reconnect lifecycleScope.launch { connectivity.connections().collect { reconnect.update(online = it) } } } diff --git a/app-shared/src/main/kotlin/io/github/maxdac/marknotes/sync/SyncManager.kt b/app-shared/src/main/kotlin/io/github/maxdac/marknotes/sync/SyncManager.kt index 3343c1b..0749f04 100644 --- a/app-shared/src/main/kotlin/io/github/maxdac/marknotes/sync/SyncManager.kt +++ b/app-shared/src/main/kotlin/io/github/maxdac/marknotes/sync/SyncManager.kt @@ -163,6 +163,9 @@ class SyncManager( /** The providers this build can connect, in [DriveProvider] order. */ val providers: List get() = DriveProvider.entries.filter { it in connectors } + /** Whether network state is worth observing: only builds with a provider declare `ACCESS_NETWORK_STATE`. */ + val canWatchNetwork: Boolean get() = providers.isNotEmpty() + /** Called whenever the activity resumes, to complete browser-based sign-ins. */ fun onActivityResumed() = connectors.values.forEach { it.onActivityResumed() } diff --git a/app/src/androidTest/kotlin/io/github/maxdac/marknotes/ReconnectSettingsFlowTest.kt b/app/src/androidTest/kotlin/io/github/maxdac/marknotes/ReconnectSettingsFlowTest.kt index 47c0e01..5ca721e 100644 --- a/app/src/androidTest/kotlin/io/github/maxdac/marknotes/ReconnectSettingsFlowTest.kt +++ b/app/src/androidTest/kotlin/io/github/maxdac/marknotes/ReconnectSettingsFlowTest.kt @@ -1,5 +1,7 @@ package io.github.maxdac.marknotes +import android.Manifest +import android.content.pm.PackageManager import androidx.compose.ui.test.junit4.createEmptyComposeRule import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag @@ -9,6 +11,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import io.github.maxdac.marknotes.ui.settings.SYNC_ON_RECONNECT_SWITCH_TAG import org.junit.After +import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Before import org.junit.Rule @@ -32,6 +35,9 @@ class ReconnectSettingsFlowTest { fun setUp() { container.settings.setGettingStartedConsumed(true) reset() + } + + private fun launch() { scenario = ActivityScenario.launch(MainActivity::class.java) } @@ -51,8 +57,23 @@ class ReconnectSettingsFlowTest { @Test fun theSwitchIsHiddenWhenTheEditionHasNoCloudProvider() { + launch() compose.onNodeWithContentDescription("Settings").performClick() compose.onNodeWithTag(SYNC_ON_RECONNECT_SWITCH_TAG).assertDoesNotExist() assertFalse(container.settings.syncOnReconnect.value) } + + /** A 0.1.x install may restore with the setting on; without ACCESS_NETWORK_STATE a callback would throw. */ + @Test + fun launchesWithoutNetworkStatePermissionWhenTheSettingIsStillOn() { + assertFalse(container.syncManager.canWatchNetwork) + InstrumentationRegistry.getInstrumentation().runOnMainSync { container.settings.setSyncOnReconnect(true) } + launch() + compose.onNodeWithContentDescription("Settings").assertExists() + assertEquals( + PackageManager.PERMISSION_DENIED, + context.checkSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE) + ) + assertEquals(PackageManager.PERMISSION_DENIED, context.checkSelfPermission(Manifest.permission.INTERNET)) + } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 61b1e4f..c9b2975 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,10 @@ - + + + + + None: ).stdout actual = set(re.findall(r"^uses-permission: name='([^']+)'", permissions, re.MULTILINE)) expected_permissions = { - "android.permission.INTERNET", - "android.permission.ACCESS_NETWORK_STATE", "android.permission.USE_BIOMETRIC", "io.github.maxdac.marknotes.libre.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION", } From dd35517750d770d5d4fc059e3a06790f52a044ca Mon Sep 17 00:00:00 2001 From: MaxDac <25101798+MaxDac@users.noreply.github.com> Date: Tue, 3 Nov 2026 15:06:05 +0000 Subject: [PATCH 2/3] Keep the MR copies in step with the live MR until 0.2.1 is released Co-Authored-By: Claude Sonnet 5.5 --- fdroid/inclusion-mr.md | 21 +++++++++++---------- fdroid/review-handover.md | 35 ++++++++++++++++++++++------------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/fdroid/inclusion-mr.md b/fdroid/inclusion-mr.md index 4f914d1..fb38460 100644 --- a/fdroid/inclusion-mr.md +++ b/fdroid/inclusion-mr.md @@ -10,8 +10,9 @@ the MR description and in sync with the recipe and the latest release: reviewers **Title**: `New app: MarkNotes` -**Posting status**: The v0.1.7 description below is ready to post; the fork pipeline passed on 2026-10-27. -The previous (v0.1.6) description was posted by the maintainer. +**Posting status**: The v0.2.0 description below is live on the MR (verified through the API on 2026-11-03: the +`description` field is identical to this block); the fork pipeline passed the same day. The previous (v0.1.7) +description described optional Dropbox sync and *NonFreeNet*. **Files** (nothing else): `metadata/io.github.maxdac.marknotes.libre.yml`, a copy of `.fdroid.yml` with `commit: vX.Y.Z` replaced by the tag's full commit hash (`git rev-parse "vX.Y.Z^{commit}"`). @@ -21,19 +22,19 @@ The previous (v0.1.6) description was posted by the maintainer. ```markdown ## New app: MarkNotes -MarkNotes (`io.github.maxdac.marknotes.libre`) is an offline-first Markdown notes app. Notes are plain `.md` files in folders, with note links, images, chord sheets and a rendered preview. Optional Dropbox sync shares the same notes folder with the MarkNotes desktop and web apps. +MarkNotes (`io.github.maxdac.marknotes.libre`) is an offline-first Markdown notes app. Notes are plain `.md` files in folders, with note links, images, chord sheets and a rendered preview. They stay on the device; the same folder format works with the MarkNotes desktop and web apps. This edition has no cloud sync. * Upstream: https://github.com/MaxDac/marknotes-android (GPL-3.0-or-later). I am the upstream author. -* Release: [`v0.1.7`](https://github.com/MaxDac/marknotes-android/releases/tag/v0.1.7), commit `e77e9c2ba65269f0b98d36d3c24766617871ec74`, versionCode 107. +* Release: [`v0.2.0`](https://github.com/MaxDac/marknotes-android/releases/tag/v0.2.0), commit `86d39a6721e9985e095398f26dcb2d26f7868c82`, versionCode 200. * Fastlane metadata (en-US title, summary, description, icon, screenshots, changelogs) lives upstream in `fastlane/metadata/android/en-US/`. -* Anti-features: NonFreeNet. Sync is optional and uses Dropbox (OAuth PKCE, no client secret), only at start-up, on *Sync now* or, if the user turns it on, when the network comes back. There is no tracking, analytics, ads or Google Play Services. Error diagnostics stay on the device unless the user reviews a report and sends it themselves. -* Permissions: `USE_BIOMETRIC` (optional app lock) only, plus AndroidX's own `DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION`. Upstream CI fails if the APK requests anything else. +* Anti-features: none. There is no sync or other network feature, no tracking, analytics, ads or Google Play Services. Error diagnostics stay on the device unless the user reviews a report and sends it themselves. +* Permissions: `USE_BIOMETRIC` (optional app lock), plus AndroidX's own `DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION`. The APK still declares `INTERNET` and `ACCESS_NETWORK_STATE`, which come from a shared library module and are not used by this edition; removing them is planned. Upstream CI fails if the APK requests anything else. ### Build notes -* The sync core is [marknotes-sync](https://github.com/MaxDac/marknotes-sync) (Rust, with UniFFI Kotlin bindings; GPL-3.0-or-later, same author), a git submodule at its release tag `v0.4.1`. It's built from source in `build` by upstream's `scripts/build-sync-from-source.sh`. That uses a pinned Rust toolchain (1.99.1, installed through the `rustup` srclib), cargo-ndk 4.1.2 and NDK r30, with `--remap-path-prefix`, no build ID and `SOURCE_DATE_EPOCH`, so the native libraries are reproducible. Gradle then takes the bindings from that checkout (`gradleprops: marknotes.sync.fromSource=marknotes-sync`) instead of the GitHub release assets. +* The sync core is [marknotes-sync](https://github.com/MaxDac/marknotes-sync) (Rust, with UniFFI Kotlin bindings; GPL-3.0-or-later, same author), a git submodule at its release tag `v0.4.1`. It's used for local note handling and is built from source in `build` by upstream's `scripts/build-sync-from-source.sh`. That uses a pinned Rust toolchain (1.99.1, installed through the `rustup` srclib), cargo-ndk 4.1.2 and NDK r30, with `--remap-path-prefix`, no build ID and `SOURCE_DATE_EPOCH`, so the native libraries are reproducible. Gradle then takes the bindings from that checkout (`gradleprops: marknotes.sync.fromSource=marknotes-sync`) instead of the GitHub release assets. * `rm` deletes what isn't built or isn't free: the `spec/` submodules (shared test fixtures, unlicensed), marknotes-sync's npm bindings and `package.json`, and its Kotlin bindings' Gradle wrapper (`gradle-wrapper.jar`, `gradlew`, `gradlew.bat`; the build uses `gradle`). -* Native code is arm64-v8a and x86_64, under 1 MB per ABI: `libmarknotes_sync.so` (built here), plus `libjnidispatch.so` from JNA 5.19.1 (Maven Central, LGPL-2.1/Apache-2.0, which the UniFFI Kotlin bindings use) and `libandroidx.graphics.path.so` from AndroidX (Compose). The signed APK is 8,279,673 bytes (about 8.28 MB), roughly 88% smaller than v0.1.5. An ABI split would save little because the native libraries are a small part of the APK. +* Native code is arm64-v8a and x86_64, under 1 MB per ABI: `libmarknotes_sync.so` (built here), plus `libjnidispatch.so` from JNA 5.19.1 (Maven Central, LGPL-2.1/Apache-2.0, which the UniFFI Kotlin bindings use) and `libandroidx.graphics.path.so` from AndroidX (Compose). The signed APK is 7,898,401 bytes (about 7.9 MB). An ABI split would save little because the native libraries are a small part of the APK. * Release builds use full-mode R8 optimization and resource shrinking. Upstream CI checks the final APK's R8 marker, exact permissions and both native ABIs, and exercises the actual minified APK on an emulator, including native sync bindings, notes, storage and PDF import. ### Auto update and reproducible builds @@ -44,8 +45,8 @@ MarkNotes (`io.github.maxdac.marknotes.libre`) is an offline-first Markdown note ### Validation * `fdroid lint` and `fdroid rewritemeta` produce no changes. -* All nine jobs in the fork pipeline pass: https://gitlab.com/MaxDac/fdroiddata/-/pipelines/2994097941. `fdroid build` ([job](https://gitlab.com/MaxDac/fdroiddata/-/jobs/17501534283)) builds versionCode 107 from `e77e9c2ba65269f0b98d36d3c24766617871ec74` and reports "compared built binary to supplied reference binary successfully". -* The quality report now confirms a full-mode release R8 marker instead of warning that R8 is missing. The remaining permission entries are `USE_BIOMETRIC` (optional app lock) and AndroidX's signature-protected receiver permission; the Fastlane metadata, reproducible-build APK, signing key, ABI/size and R8-marker entries are informational. +* All nine jobs in the fork pipeline pass: https://gitlab.com/MaxDac/fdroiddata/-/pipelines/3021646859. `fdroid build` ([job](https://gitlab.com/MaxDac/fdroiddata/-/jobs/17692692121)) builds versionCode 200 from `86d39a6721e9985e095398f26dcb2d26f7868c82` and reports "compared built binary to supplied reference binary successfully". +* The remaining permission entries in the quality report are explained above; the Fastlane metadata, reproducible-build APK, signing key, ABI/size and R8-marker entries are informational. ## Checklist diff --git a/fdroid/review-handover.md b/fdroid/review-handover.md index 54c4de7..6d66956 100644 --- a/fdroid/review-handover.md +++ b/fdroid/review-handover.md @@ -7,25 +7,26 @@ and the live MR description in `fdroid/inclusion-mr.md`. This file adds the stat pitfalls. Delete it with the other inclusion-only docs in [#21](https://github.com/MaxDac/marknotes-android/issues/21) once the MR is merged. -## State at handover (2026-10-27) +## State at handover (2026-11-03) | What | Value | | --- | --- | -| MR | Open, no labels or reviewer comments. Metadata and live description are updated to v0.1.7; the public API confirms the description matches `fdroid/inclusion-mr.md` | -| Fork branch head | `440cdaa3e8`: one commit, `New app: MarkNotes`, same parent `25d23ee7ce82bd09c71f6579cd3ca2e7e7fb9117`; no rebase | -| Fork pipeline | [2994097941](https://gitlab.com/MaxDac/fdroiddata/-/pipelines/2994097941): all 9 jobs passed | -| `fdroid build` job | [17501534283](https://gitlab.com/MaxDac/fdroiddata/-/jobs/17501534283): "compared built binary to supplied reference binary successfully", unchanged allowed signer `ffafda02…70e4` | -| Release in the MR metadata | [v0.1.7](https://github.com/MaxDac/marknotes-android/releases/tag/v0.1.7), commit `e77e9c2ba65269f0b98d36d3c24766617871ec74`, versionCode 107 | -| Release APK | `marknotes-libre-0.1.7.apk`, SHA-256 `d4dd120b3c696cc5b9634c9504c7f225fc75318c873ae08de3cfc9394f20c360`, 8,279,673 bytes (8.28 MB) | -| Release workflow | [40494238120](https://github.com/MaxDac/marknotes-android/actions/runs/40494238120): all release gates, signature pinning and signed/unsigned comparison passed | +| MR | Open, label `New App`. On 2026-10-27 the reviewer `seekme-seekyou` closed it under the 3-month maintenance policy and the maintainer reopened it citing FPInk (!50122), offering to wait three months; no reply since. Metadata and live description are updated to v0.2.0; the public API confirms the description matches `fdroid/inclusion-mr.md`. An update comment is drafted for the maintainer to post; check the MR notes for whether it was | +| Fork branch head | `146017843f`: two commits on the same parent `25d23ee7ce82bd09c71f6579cd3ca2e7e7fb9117` (`New app: MarkNotes`, then `Update MarkNotes to 0.2.0`, pushed through the GitLab API); no rebase | +| Fork pipeline | [3021646859](https://gitlab.com/MaxDac/fdroiddata/-/pipelines/3021646859): all 9 jobs passed | +| `fdroid build` job | [17692692121](https://gitlab.com/MaxDac/fdroiddata/-/jobs/17692692121): "compared built binary to supplied reference binary successfully", unchanged allowed signer `ffafda02…70e4` | +| Release in the MR metadata | [v0.2.0](https://github.com/MaxDac/marknotes-android/releases/tag/v0.2.0), commit `86d39a6721e9985e095398f26dcb2d26f7868c82`, versionCode 200 | +| Release APK | `marknotes-libre-0.2.0.apk`, SHA-256 `5d38152a1a0c2049e88c3a2103d3237008be9d29751470d84e41a4751f33f7b3`, 7,898,401 bytes (7.9 MB) | +| Release workflow | [41608941558](https://github.com/MaxDac/marknotes-android/actions/runs/41608941558): all release gates, signature pinning and signed/unsigned comparison passed | | Quality report | Missing-R8 warning replaced with an informational full-mode release R8 marker; required permissions and metadata/signing/ABI entries remain explained | | Signing certificate | SHA-256 `ffafda02fa9af06b1927095c8d843ae862173512d4cdaca09bbaaef84d1d70e4` (`AllowedAPKSigningKeys`, repo variable `RELEASE_CERT_SHA256`). It must never change | -| Permissions | `USE_BIOMETRIC`, plus AndroidX's `DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION`. Checks / F-Droid build fails on anything else | +| Anti-features | None: no sync and no network feature since 0.2.0 (Dropbox moved to MarkNotes Plus, marknotes-android#61); `NonFreeNet` was removed from the recipe | +| Permissions | `INTERNET`, `ACCESS_NETWORK_STATE`, `USE_BIOMETRIC`, plus AndroidX's `DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION`. `INTERNET` and `ACCESS_NETWORK_STATE` are merged in from `:app-shared` and unused by this edition; the MR description says removal is planned. Checks / F-Droid build fails on anything else | | Native libraries per ABI (arm64-v8a, x86_64) | `libmarknotes_sync.so` (built from source, about 0.7 MB), `libjnidispatch.so` (JNA 5.19.1 from Maven Central), `libandroidx.graphics.path.so` (AndroidX) | | marknotes-sync | Submodule `marknotes-sync/` at tag v0.4.1 (`5e68e80ab6f0731767334e9e7e967fd01e793450`), catalog `marknotesSync = "0.4.1"` | | Toolchain pins | Rust 1.99.1 (through fdroiddata's `rustup@1.29.2` srclib), cargo-ndk 4.1.2, NDK r30. Identical in `.fdroid.yml`, `scripts/build-sync-from-source.sh` and `.github/actions/setup-sync-toolchain` | | `rm:` | `spec`, `marknotes-sync/spec`, `marknotes-sync/bindings/npm`, `marknotes-sync/package.json`, and the sync Kotlin bindings' `gradle-wrapper.jar`, `gradlew` and `gradlew.bat` | -| MarkNotes Pro | Last verified adoption: `libre/` at v0.1.5 ([MaxDac/MarkNotes#49](https://github.com/MaxDac/MarkNotes/pull/49)); this release does not update Pro's pin | +| MarkNotes Pro | Owns Dropbox since [MaxDac/MarkNotes#53](https://github.com/MaxDac/MarkNotes/pull/53); `libre/` re-pin to v0.2.0 in [MaxDac/MarkNotes#54](https://github.com/MaxDac/MarkNotes/pull/54) | Before acting, check this is still true: compare the MR's head SHA and the latest GitHub release with the table. @@ -44,6 +45,9 @@ Before acting, check this is still true: compare the MR's head SHA and the lates - v0.1.6 enabled full-mode R8 and resource shrinking, reducing the signed APK by 87.90% while preserving permissions, both ABIs and signing identity. The final minified APK is exercised on an emulator, and CI checks its R8 marker, native libraries and exact permissions. No sync/spec pins or public APIs changed. +- v0.1.7 fixed the folder drawer opening and white screen transitions in dark mode. +- v0.2.0 removed Dropbox sync (moved to MarkNotes Plus, marknotes-android#61): no sync provider, no `NonFreeNet`, + APK 7.9 MB. The recipe's `AntiFeatures` block was removed; the build entry and `CurrentVersion` moved to 0.2.0 / 200. ## Lessons from FPInk (fdroid/fdroiddata!50122, same author, merged) @@ -61,7 +65,7 @@ All requests below were addressed before submission. Keep them true, and expect | **MR description out of date** (FPInk updated the Fastlane text instead) | Update the description in `fdroid/inclusion-mr.md` **and** give it to the maintainer to paste into the MR. Verify afterwards: the MR's `description` field from the public API must contain the new version and pipeline id | | Prebuilt native libraries, telemetry | Only JNA's and AndroidX's libraries are prebuilt, both from Maven repositories F-Droid trusts; no telemetry or trackers | | `sudo` line suggestion (`apt-get install -y …`) | Ours already uses `apt-get update` and `apt-get install -y gcc libc-dev` | -| Testers: on-device behaviour, permissions requested when needed | No network permissions (offline only); the biometric lock is opt-in | +| Testers: on-device behaviour, permissions requested when needed | No network use at all since 0.2.0 (no sync feature); the biometric lock is opt-in | ## Pitfalls @@ -69,7 +73,10 @@ All requests below were addressed before submission. Keep them true, and expect `https://gitlab.com/fdroid/fdroiddata/-/merge_requests/54034/discussions.json` (the notes API answers 401 or an empty list anonymously). Raw job logs: `https://gitlab.com/MaxDac/fdroiddata/-/jobs//raw` with a browser user agent. Only the maintainer can comment, resolve threads or edit the description: hand over fenced - copy-paste blocks. + copy-paste blocks. For 0.2.0 the maintainer lent a short-lived token with the `api` scope (revoked afterwards) + and the agent pushed the recipe through `POST /projects/86842778/repository/commits` and edited the description; + posting the comment was blocked by the session's write guard, so it stayed with the maintainer. Never copy + descriptions from a terminal: line wrapping drops text. Pass a file instead. - **Public report artifacts**: the job-detail/trace API can answer 401, and the archive-file endpoint can answer 404 for report-only artifacts. Read logs through the web raw endpoint above. Download quality JSON through `https://gitlab.com/MaxDac/fdroiddata/-/jobs//artifacts/download?file_type=codequality`. @@ -80,7 +87,9 @@ All requests below were addressed before submission. Keep them true, and expect paths against the process directory, not PowerShell's location). Use `.\gradlew.bat`, and set `JAVA_HOME` to Android Studio's `jbr` for `apksigner`. - **Two copies of the recipe**: `.fdroid.yml` (PR here, `commit: vX.Y.Z`) and the fork's metadata file (full - hash). Apart from `commit`, keep them identical. + hash). Apart from `commit`, keep them identical, except that the fork's file is in `fdroid rewritemeta` format + (`Binaries:` on its own line with the URL indented below). Build the fork's file from the fork's current copy, + not from `.fdroid.yml`, or `rewritemeta` will report changes. - **Upstream changes that alter the APK** need a new release (Prepare release, merge its PR, then Release on `main`). The release PR is opened by a bot, so its CI runs through a workflow_dispatch "PR CI" run. Roll forward only; versionCode can't go down. From 119e07d4dc5fda485b577925f5750681fbaacb9c Mon Sep 17 00:00:00 2001 From: MaxDac <25101798+MaxDac@users.noreply.github.com> Date: Tue, 3 Nov 2026 15:13:46 +0000 Subject: [PATCH 3/3] Update the release APK check fixture for the dropped permissions Co-Authored-By: Claude Sonnet 5.5 --- scripts/test-release-apk.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/test-release-apk.py b/scripts/test-release-apk.py index af95929..3d2cca2 100644 --- a/scripts/test-release-apk.py +++ b/scripts/test-release-apk.py @@ -31,8 +31,6 @@ def check_fixture(self, *, marker=True, debug=False, missing_native=False, extra if extra_native: archive.writestr("lib/armeabi-v7a/libunexpected.so", b"ELF") permissions = [ - "android.permission.INTERNET", - "android.permission.ACCESS_NETWORK_STATE", "android.permission.USE_BIOMETRIC", "io.github.maxdac.marknotes.libre.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION", ]