Files
foto-duplicate-finder/docs/ROADMAP.md
T
SoftwareApp 55101886cb docs: finish the Console pre-flight and retire the screenshot blocker
Everything the Play forms ask for that can be settled from the working copy, settled:

- PRIVACY.md now carries the real contact address (softwareapp.hb@gmail.com), so the
  policy publishes as-is. Hosting is the one step left that cannot be done from here,
  because the repo has no git remote: step 5 of the checklist spells out the push +
  GitHub Pages route and the URL it yields.
- Checklist: the screenshot item is done (four captures, 1080x2160, over the demo
  library, no permission banner), the keystore and versionCode are recorded as already
  in place - including the upload key's SHA-256 for the Play App Signing enrolment - and
  the App access / Ads / remaining App content declarations are written down. The
  first-upload release-notes question is answered too: versionCode is already 2, so
  1.0.1 takes the *initial release* block, not the 1.0.1 one.
- The build facts were verified against the artefacts rather than the source: the
  release APK reports versionCode=2, versionName=1.0.1, targetSdk=36 and exactly the
  documented permission set, with no INTERNET in what actually ships.
- ROADMAP: the re-shoot row is struck out with the numbers that closed it, M10 no longer
  claims the captures predate the demo library, and the demo library's removal is
  recorded (MediaStore 5,386 -> 5,364; pruneTo drops the stale index rows next scan).

The staged state and the system confirmation dialog are documented as deliberately not
captured: staging clears the demo group, which would put real photos back in frame.
2026-10-13 23:59:15 +02:00

7.3 KiB
Raw Blame History

Roadmap

Foto Dup Finder is built against a fixed milestone plan (M0–M10). This file is the single source of truth for what is done and what remains. Before this file existed the plan lived only in developer notes, and the CHANGELOG refers to milestone labels (M7, M8).

Legend: ✅ done · 🚧 in progress · ⏳ not started · 💡 candidate (proposed, not scheduled)

Milestones

# Milestone Status Evidence / notes
M0 Toolchain & scaffold ✅ User-local SDK, Gradle 9.3.1 + AGP 9.1.0, wrapper and version catalog, empty debug APK builds
M1 Pure-Kotlin duplicate engine + JVM tests ✅ SHA-256 quick/full hashing, pHash + RGB dHash, union-find grouping, Exact/Balanced/Aggressive policies; tests under app/src/test
M2 Data layer ✅ MediaStore query, coroutine-parallel hashing, incremental SQLite hash index, permission + partial-access handling
M3 Compose UI ✅ Scan progress, grouped results with thumbnails, two-step staging, selection helpers
M4 Safe deletion ✅ MediaStore.createDeleteRequest / createTrashRequest; legacy direct delete on Android 10
M5 Performance pass ✅ Size short-circuit, provider-thumbnail hashing, batched hashing (folded into M2/M3)
M6 Release config & Play readiness ✅ R8/minify, optional signing, .aab, PLAY_RELEASE_CHECKLIST, PRIVACY.md, README
M7 On-device validation & hardening ✅ Foreground dataSync service, one failure boundary per file, core/DecodeMath, hostile fixtures (EDGE_CASE_TESTING), versionName → 1.0.0
M8 Localisation groundwork ✅ Every user-visible string in res/values/strings.xml with real <plurals>, shared text/TextRes.kt; English is the only locale
M9 Monetisation ⏳ No billing code. Blocked on a product decision (free forever vs. one-time unlock vs. limited free scans); changes the Data Safety answers
M10 Store assets ✅ tools/make-store-assets.sh renders the 512×512 icon and the 1024×500 feature graphic from the app's own vectors; tools/make-store-screenshots.sh normalises device captures to Play's 2:1 limit (1080×2160); tools/make-demo-photos.py generates the photo library the results capture is taken over, so no real photos are published; all listing copy is drafted and length-checked in STORE_LISTING. Four screenshots (empty, scanning, results, Options) were re-shot after a real permission grant and normalised to 1080×2160; screenshots are deliberately not committed

Release blockers (work that is not a numbered milestone)

Item Detail
Large-library scan stall ✅ fixed — verified on device The 5,363-photo library used to freeze at “74 / 100 · Comparing file signatures…” (a blocked syscall: zero CPU, workers sleeping). BoundedCall now abandons a stuck provider call and trips a stall breaker after three consecutive timeouts. Verified on the real test device (Android 12 / MIUI): the scan completes — 20.2 s cold, 11.0 s warm, 15 groups, 6 unreadable — where it used to hang. Covered by BoundedCallTest and ScanEngineEndToEndTest
Unreleased BoundedCall fix ✅ The provider-call deadline shipped in 1.0.1 (versionCode 2)
Play Console: foreground-service declaration Declaring the dataSync type (App content › Foreground service permissions) is release-blocking. [code] the manifest already declares it; the ready-to-paste justification is in PLAY_RELEASE_CHECKLIST step 5 — this is a Console action, not a code change
Remaining Play paperwork Keystore / Play App Signing, Data Safety form, hosted privacy policy and store listing — see PLAY_RELEASE_CHECKLIST
Store screenshots need one re-shoot ✅ done — verified on device The old captures showed the Photo access needed banner (an app-op is not a grant) and 03-results.png showed the developer's own library. After a real grant the set was re-shot over the generated demo library: 01-empty (no tap needed — results and selection live in ScannerViewModel, so force-stop + relaunch returns to the empty state with the grant still in place), 02-scanning (5353 / 5385), 03-results (17 groups, 54 of 5385 photos, 83.5 MB reclaimable; the headline group's 43.1 MB out-ranks the device's own 39.8 MB, so no real photo is in frame) and 04-options. Captures are taken by watching for UI text changes, because MIUI refuses synthetic taps (INJECT_EVENTS). The demo library is deleted from the test device afterwards — MediaStore 5,386 → 5,364 rows, and the 22 stale rows it leaves in the hash index are pruned by PhotoIndexStore.pruneTo on the next scan. Deliberately not captured: the staged state and the system confirmation dialog, because staging clears the demo group and would put real photos back in frame. Details: STORE_LISTING
Device-only verifications RAW/DNG files, a real iPhone HEIC with a non-upright EXIF orientation, permission revoked mid-scan, and the Android 14 “Selected photos” flow. ✅ The index-reuse check is done: after a scan the index holds 5,363 rows, 5,357 with a perceptual hash, and a second scan runs in 11.0 s vs 20.2 s cold

Candidate milestones (proposed, not scheduled)

Candidate Rationale
Colour signature Flat-colour images (blank scans, dark UI screenshots) hash identically regardless of colour, so they are reported as mutual duplicates — see the “Known gap” in EDGE_CASE_TESTING. A small colour histogram is orthogonal to the grayscale structure hash, and the benchmark harness already has an implementation to measure against
Video support Only images are scanned today (README “Known limitations”)
“Similar shots” section Bursts / same-subject shots as a separate, never-pre-selected list. Needs a different algorithm and must not feed the deletion flow
Warm-scan speed A second scan is faster (11.0 s) but its “similar” stage still costs ~10 s even though all 5,357 perceptual hashes are cached — the per-photo withTimeoutOrNull wrapper still runs for all 5,363 photos. Profiling this could make an unchanged library re-scan in well under a second

Suggested order

  1. ✅ Done — the scan-stall hardening and the BoundedCall fix shipped as 1.0.1 (versionCode 2). Only the on-device re-check of the 5,363-photo scan remains.
  2. Finish the Play Console paperwork and roll out through internal → closed → production.
  3. ✅ Done — M10 store assets: the icon and feature graphic are generated from the app's own vectors, the screenshot normaliser enforces Play's 2:1 limit, the listing copy is drafted and length-checked, and the demo library that keeps real photos out of the results capture is generated and validated. The re-shoot after a real permission grant is done as well (see blockers): four captures, normalised to 1080×2160.
  4. Colour signature — the flat-colour false-positive fix.
  5. M9 — monetisation, once the product decision is made.

Deliberately deferred: video support and the “similar shots” section.