An Android duplicate-photo finder. Pure-Kotlin core (SHA-256 + dHash hashing, exact and perceptual union-find grouping, three sensitivity presets) with MediaStore access, a persistent SQLite hash index and a tiered ScanEngine behind a Jetpack Compose UI. No INTERNET permission, no ads, no analytics, no account. Ships versionCode 2 / versionName 1.0.1, carrying the scan-stall hardening (BoundedCall breaker) and verified against a 5,363-photo device library. 80 JVM unit tests, lint clean. Release paperwork is in docs/ (Play checklist, store listing, edge-case testing), the upload-ready Play graphics and screenshots in store-assets/, and the generators for those assets in tools/.
7.2 KiB
Play Store listing (M10)
Everything the Main store listing page in the Play Console asks for, ready to copy. The graphics are generated from the app's own vectors, so they can be regenerated after any branding change instead of being edited by hand.
See PLAY_RELEASE_CHECKLIST § 4 for where these fit in the release.
Graphics
Generated into store-assets/ inside the repository, never into $HOME. These PNGs are
the files that get uploaded to the Play Console, so they are committed and versioned with the
release they belong to. Only the uncropped captures the screenshots are cut from
(store-assets/raw-screenshots/) are git-ignored.
./tools/make-store-assets.sh # icon + feature graphic -> store-assets/
./tools/make-store-screenshots.sh # crop the adb captures -> store-assets/screenshots/
| Asset | Play's spec | Result | Source |
|---|---|---|---|
| App icon | 512×512, 32-bit PNG with alpha, ≤ 1 MB | store-assets/icon-512.png — 512×512, RGBA, 6 KB |
tools/store-assets/icon.svg, mirrored from app/src/main/res/drawable/ic_launcher_background.xml + ic_launcher_foreground.xml (paths and group transforms are identical, so the store icon is the launcher icon) |
| Feature graphic | 1024×500, JPEG or 24-bit PNG without alpha | store-assets/feature-graphic-1024x500.png — 1024×500, RGB |
tools/store-assets/feature-graphic.svg |
| Phone screenshots | JPEG or 24-bit PNG without alpha, shorter side ≥ 320 px, longer side ≤ 3840 px, longer side ≤ 2× shorter side | store-assets/screenshots/0{1,2,3}-*.png — 1080×2160, RGB |
tools/make-store-screenshots.sh, from raw adb exec-out screencap captures in store-assets/raw-screenshots/ |
Two details worth keeping in mind:
- The icon is full bleed: the adaptive-icon background covers all 108 dp, so every pixel of the PNG ends up opaque and Play's mask can crop to any shape without showing a gap.
- A phone screenshot at the test device's native 1080×2400 is 2.22:1 and therefore
rejected — Play's limit is 2:1. The normaliser crops the status bar (93 px) and the
navigation bar (130 px) and clamps to 1080×2160, which removes system chrome only, never
app content. Re-measure both insets if you shoot on another device and pass
STATUS_BAR_PX/NAV_BAR_PX.
Screenshots
Captured on the test device (1080×2400, Android 12 / MIUI), cropped to 1080×2160:
| File | Screen |
|---|---|
01-empty.png |
Intro / empty state before the first scan |
02-scanning.png |
Scanning progress — “Looking for visually similar photos…” 5341 / 5363 |
03-results.png |
Results — 15 duplicate groups, 39.8 MB reclaimable, BEST badge, selection bar |
⚠️ Two caveats before these go live.
- All three still show the “Photo access needed” banner. The test device (Xiaomi M2101K7AG
/ MIUI) refuses
pm grant,install -gand synthetic taps, so the runtime permission was never actually granted there — the scan was driven with a debug-onlyMANAGE_EXTERNAL_STORAGEapp-op, which the app's permission check does not honour (see EDGE_CASE_TESTING). Re-shoot on a clean install after tapping “Grant access”: that also closes the one remaining device-only verification gap. - The results screenshot shows real photos from the test library. They are the developer's own, so they can be published, but check that nothing personal is recognisable first.
Screens still worth capturing once a device grants access through the real flow — each needs one tap, which is exactly what the MIUI test device cannot be driven to do:
- the Options sheet with the three sensitivity presets,
- the staged state (“All duplicates are ready to go”) after selecting duplicates,
- Android's confirmation dialog for moving photos to the trash.
Store listing copy
App name (max 30 characters)
Foto Dup Finder
Short description (max 80 characters)
Find and remove duplicate photos. Free, ad-free, works offline.
Alternative, if the no-upload promise should lead:
Find and delete duplicate photos. No ads, no uploads, no account.
Full description (max 4000 characters)
Find and remove duplicate photos, then get the space back. Foto Dup Finder does one
job and does it without ads, without an account and without ever uploading anything.
WHY IT IS DIFFERENT
• No ads — no banners, no interstitials, no upsells.
• No internet permission at all, so the app has no way to upload your photos.
• No account, no sign-in, no tracking, no analytics.
WHAT IT DOES
• Finds exact copies: files that are byte-for-byte identical, even when their names
differ.
• Finds near-duplicates: the same picture saved twice, or two shots of the same
scene. A perceptual hash compares how the images actually look rather than only
their bytes, so a re-compressed or slightly re-cropped copy is still caught.
• Suggests what to keep. The best copy of each group is marked BEST, and
"Select all but best" marks the rest in a single tap.
• Lets you choose how far to go: Exact copies only, Balanced (recommended) or
Aggressive.
• Deletes safely. Every removal goes through Android's own confirmation dialog, and
you choose between moving photos to the trash (recoverable) and deleting them
permanently. Nothing is removed without your confirmation.
• Copes with a messy library. RAW files and unreadable or corrupt images are skipped
and reported instead of stopping the scan.
• Much faster the second time. File hashes are remembered on the device, so
re-scanning an unchanged library takes a fraction of the first run.
HOW IT WORKS
1. Grant photo access — everything runs on your device.
2. Tap Start scan. A library of several thousand photos is done in seconds.
3. Review the groups, keep the copies you want and select the rest.
4. Tap "Remove all selected" and confirm once for the whole batch.
YOUR PHOTOS STAY YOURS
Foto Dup Finder declares no internet permission, ships no advertising or analytics
SDKs, and stores nothing besides a local index of file hashes so the next scan can be
quick. No photo ever leaves your phone, and uninstalling the app removes the index
with it.
GOOD TO KNOW
• Requires Android 8.0 or newer.
• Photos moved to the trash can be restored from your gallery app until the trash is
emptied (Android 11 and newer).
• Works fully offline, in flight mode, always.
Every claim above is checkable against this repository: no INTERNET permission in the
merged manifest, no ad or analytics dependency in gradle/libs.versions.toml, minSdk 26
in app/build.gradle.kts, and the second-run speed-up is the persistent hash index in
data/PhotoIndexStore.kt.
Category and tags
- Category: Tools (alternative: Photography — Tools matches the one-job utility framing better, and Photography is more crowded).
- Tags: duplicate photos, photo cleanup, storage.
- Contact details: the same e-mail as in PRIVACY.md — replace the
support@example.complaceholder there first.