diff --git a/docs/PLAY_RELEASE_CHECKLIST.md b/docs/PLAY_RELEASE_CHECKLIST.md index 180d339..fee4303 100644 --- a/docs/PLAY_RELEASE_CHECKLIST.md +++ b/docs/PLAY_RELEASE_CHECKLIST.md @@ -212,17 +212,79 @@ action except the items marked **[code]**. (Match ` **Read media images** — Describe why the Android photo picker or other system pickers do + > not fully support your app's core functionality, and why your app needs to use the + > `READ_MEDIA_IMAGES` permission. + + Answer the *picker* half first and the *permission* half second — that is the order the + question asks in, and the picker half is the one a reviewer is actually weighing. Paste + (1 689 chars): + + > Foto Dup Finder's core functionality is finding duplicate and visually similar photos + > across the user's whole photo library, and letting the user delete the copies. Reading the + > photo library is that entire operation. + > + > The Android photo picker cannot support it. The picker only grants access to the items the + > user selects in that session, and the duplicates are by definition outside that selection: + > the copies of a picked photo are other files, in other folders, that the picker never hands + > over. Comparing only the picked items tells the user nothing they did not already know; + > finding the groups requires comparing every photo in the library with every other photo. + > + > The app also needs the whole library to calibrate itself. It estimates the distance + > distribution of unrelated photos from the library's own pairwise perceptual-hash distances + > and sets the widest similarity threshold whose expected number of accidental matches stays + > in budget. Below a few hundred pairs that fit is impossible and the app falls back to a + > default; over a hand-picked subset it would be meaningless, because a selection is not a + > sample of the library — it is the part the duplicates are not in. + > + > Nothing is uploaded: the app declares no INTERNET permission and all hashing happens on the + > device. Photos are only read. Any deletion requires an explicit selection by the user and + > always goes through Android's own confirmation dialog (`MediaStore.createDeleteRequest` / + > `createTrashRequest`) — never silently — and the app never requests + > `MANAGE_EXTERNAL_STORAGE`. Partial access (`READ_MEDIA_VISUAL_USER_SELECTED`) is declared + > and supported as a reduced mode for users who prefer to hand over fewer photos. + + If the box is smaller, this says the same in 682 chars: + + > Foto Dup Finder's only function is finding duplicate and similar photos across the user's + > whole photo library and letting them delete the copies: reading the library is the feature. + > The Android photo picker grants access only to the items a user selects, while the + > duplicates of a picked photo are precisely the files the picker never hands over — a + > selection cannot answer "what is duplicated in my library". The app also calibrates its + > similarity threshold from the library's own pairwise distance distribution, which needs the + > whole library to mean anything. Nothing is uploaded (the app has no INTERNET permission) + > and deletion always goes through Android's confirmation dialog. + + Nothing in either version is aspirational — every claim is a place in the code, so it can be + defended if Play comes back with questions: + + | The answer claims … | … and this is where that is true | + |---|---| + | the app reads the whole library | `MediaStorePhotoSource.loadPhotos()` queries the image collection with a `null` selection — metadata only, pixels later and lazily | + | duplicate detection needs every pair | `DuplicateGrouper` clusters by exact SHA-256 *and* perceptual fingerprints, union-find over the whole scan | + | the threshold is fitted per library | `AdaptiveThreshold.choose()` estimates the unrelated-photo distribution from the scan's own pairwise histogram and returns the widest threshold keeping the *expected* number of accidental matches in budget; under `MIN_SAMPLE` = 200 pairs it falls back to the policy ceiling | + | a selection cannot stand in for a library | that histogram is built from every pair in the scan — the phone's 5,385-photo library fitted to *pHash ≤ 10 bits* and said so on screen, while the 22-photo tablet library was too small to fit and used its ceiling (`(default)`) | + | a constant threshold cannot work | `AdaptiveThreshold` KDoc and README § scan pipeline: the widest pHash threshold with zero false pairs falls from **13 (120 images) to 5 (2 880)** as a library grows | + | nothing leaves the device | no `INTERNET` in the merged release manifest — the `aapt2` audit in the item above | + | deletion is never silent | `MediaDeleter` → `MediaStore.createDeleteRequest` / `createTrashRequest`; no `MANAGE_EXTERNAL_STORAGE` | + | partial access is real, not a gesture | `AppPermissions.MediaAccess.PARTIAL` / `hasPartialAccess()`, with a banner and a prompt to upgrade to full access | + + Three notes for filling the form in. (1) The declaration asks about `READ_MEDIA_IMAGES` only: + `READ_MEDIA_VISUAL_USER_SELECTED` is the *narrow* half of the pair and needs no justification, + so do not volunteer it as though it were the broad one. (2) If that screen also asks for a + **video**, the recorded foreground-service demo does show a full 5,385-photo library scan and + the groups it found, which is the functionality this declaration is about — but it was filmed + to answer the foreground-service question, so it is a fallback rather than the intended + answer, and a take focused on this question (open → grant → scan → groups) may be asked for. + (3) If the declaration were refused, the app already runs in the partial mode it implements + (banner + subset scan), so nothing would need rewriting — what would be lost is the promise + ("find the duplicates you forgot about"), which is why the answer above is the one to stand + behind. - > Foto Dup Finder is a photo-cleanup tool. Reading the photo library **is** its core - > functionality: it compares photos on the device to find exact and near duplicates - > and lets the user remove the copies they select. Nothing is uploaded — the app - > declares no network permission. Deletion always goes through the system consent - > dialog (`MediaStore.createDeleteRequest`); the app never requests - > `MANAGE_EXTERNAL_STORAGE`. - [ ] Declare the **foreground service type**. This is the release-blocking item for the Android 14+ review. **[code]** The manifest already matches what you will declare: