diff --git a/AGENTS.md b/AGENTS.md index a800bf7..f3909d9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -118,6 +118,8 @@ comment. `app/src/test/java/…/ProjectInvariantsTest.kt` holds the source-level | `docs/EDGE_CASE_TESTING.md` | What was verified, on which device or AVD, and what was not | | `docs/PERFORMANCE.md` | The numbers that exist, and the ones that deliberately do not yet | | `docs/PLAY_RELEASE_CHECKLIST.md` | Release only — work it in order | +| `docs/RELEASE_NOTES.md` | The Play **What's new** copy, at 500 characters, and the longer announcement | +| `docs/PERMISSION_VIDEO.md` | The declaration's 23-second video: what it shows, and how to re-shoot it | | `docs/COMPETITIVE_ANALYSIS.md`, `docs/MONETISATION.md`, `docs/STORE_LISTING.md` | Product context, pricing, and length-checked listing copy | | `PRIVACY.md` | The policy text the store listing points at | diff --git a/CHANGELOG.md b/CHANGELOG.md index 850f34c..1cda9bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,42 @@ upload needs a **higher `versionCode`** than every previous upload. ### Added +- **The release notes exist before the release does, and their length is measured.** `docs/RELEASE_NOTES.md` + holds the copy for the Play Console's **What's new** field — **450 characters**, against Play's 500 + limit, counted rather than estimated — plus a longer announcement for a website or GitHub release. + The Console field renders neither Markdown nor bullets, so the copy is plain sentences; the first + release's text leads with the claim the survey says no competitor makes (no ads, no `INTERNET` + permission) and then proves it is still a file manager, because "private" without the verbs reads as + a promise rather than a product. The file also says how the next one is written: move `[Unreleased]` + into a version heading **first**, so the changelog and the notes cannot describe different builds. + + Two stale claims were found while writing it and fixed rather than repeated in the announcement: the + README's known limitations still said the trash had no "empty it now" action and that expiry waited + for the next delete (both changed in M19), and its status paragraph still stopped at M15. + +- **The Permissions Declaration Form's answer is written down, at 490 characters.** Section 5 of + [PLAY_RELEASE_CHECKLIST](docs/PLAY_RELEASE_CHECKLIST.md) now holds the justification for + `MANAGE_EXTERNAL_STORAGE`, measured against the form's 500-character field. It argues from what the + Storage Access Framework and `MediaStore` cannot do — a grant covers one picked tree, neither + enumerates volumes or presents a whole-device hierarchy, and `MediaStore` exposes media collections + rather than arbitrary files and folders — and never from speed, which Google's own wording rejects. + The app's existing SAF fallback and its single best-effort delete are named, so neither API is + dismissed as useless; it is only unable to carry the core purpose. + +- **The permission-use video is recorded, captioned and cut for privacy.** Play's form also asks for a + short video of the permission in use; `store-assets/video/permission-use.mp4` is **19.7 s** at + 1080×2400 with six burned-in English captions (no audio), beside an uncaptioned twin and the `.srt`. + It shows the app's prompt, Android's grant screen (the phone's system UI is German, translated in the + captions), internal storage, non-media documents and a folder created through the same permission. + The raw take also showed the home screen, whose library counts are the owner's real totals, so the + published copy **cuts that segment** and resumes on the internal-storage listing. That is a privacy + redaction, not an edit to the evidence, and both copies share it. + + Two traps are written down with it: the emulator under software emulation captures **one frame in + three seconds**, so the video has to come from the phone; and after Settings grants the app-op, a + package-level `appops set … deny` leaves the UID mode allowed, so only + `appops set --uid … deny` brings the prompt back. + - **A dot marks what an operation just produced.** Copy something, extract an archive, rename a file or make a folder, and the rows it created carry a small dot until you open that folder again — so "what did that 4 GB copy do?" has an answer, including when the copy went to a folder you were not looking at. The mark lives in diff --git a/README.md b/README.md index a0f2054..a559900 100644 --- a/README.md +++ b/README.md @@ -12,25 +12,23 @@ no advertising or tracking SDKs. ## Status -**M15 — favourites: done.** A star in any folder's app bar puts that folder on the home screen, above -the libraries, where a folder that has gone is marked *not found* rather than silently dropped and -the row's own star removes it. Before it: **M14 — renaming a batch** (selecting several files and -choosing Rename opens a **pattern** dialog instead of the single-file one: one name, the number to -start at, and a preview of the names -it will produce — and the numbering **carries on from the folder's own**, so `series (1)` and -`series (2)` already there make the next batch start at 3. A batch that cannot be done is refused -before anything moves, naming the file in the way, and the whole batch is one undo step. Before it: -**M13 — video thumbnails** (a video row shows a frame of the video itself rather than a film icon — -the platform's own retriever, through Coil, with a frame taken a second in and the icon kept as the -fallback when a file cannot be decoded), **M12 — duplicate finder** (size, then the head of every -candidate, then the survivors in full), **M11 — library views** (a **Libraries** section on the -root screen — Images, Videos, Audio, Documents, Archives and Recent — whose rows open -ordinary listings of those files, each carrying a count from the search index), and **M10 — store -assets**: the app is named **Plain Files**, the icon and feature graphic come from the app's own -vectors, and [docs/STORE_LISTING.md](docs/STORE_LISTING.md) holds length-checked listing copy. -Screenshots exist for 5 phone (1080×2160), 4 on a 7-inch tablet (1200×1920) and 4 on a 10-inch -tablet (2560×1600) — but they **predate M11**, so the home screen in them is the old volume list and -they will need re-shooting, which the library views make a privacy question as well as a cosmetic one +**M20 — launcher shortcuts: done.** Long-press the app icon for **Images**, **Recent** and **Trash**. +Since the last release build: **M19** gave the trash its 24-hour window, **Empty trash** and a sweep on +open; **M18** let compression store files instead of squeezing them; **M17** put a dot on the rows an +operation produced; and **M16** added **Copy to… / Move to…** with a destination picker that does not +take you out of the folder you are in. Before them: **M15 — favourites** (a star puts a folder on the +home screen), **M14 — batch rename** (one pattern, a start number, and a series that carries on from +the folder's own), **M13 — video thumbnails** (a frame of the video itself rather than a film icon), +**M12 — duplicate finder** (size, then the head of every candidate, then the survivors in full), +**M11 — library views** (Images, Videos, Audio, Documents, Archives and Recent, counted from the +search index), and **M10 — store assets**: the app is named **Plain Files**, the icon and feature +graphic come from the app's own vectors, [docs/STORE_LISTING.md](docs/STORE_LISTING.md) holds +length-checked listing copy, and [docs/RELEASE_NOTES.md](docs/RELEASE_NOTES.md) holds the copy for the +Console's **What's new** field. + +Screenshots exist for 8 phone (1080×2160), 4 on a 7-inch tablet (1200×1920) and 4 on a 10-inch +tablet (2560×1600). The phone set was re-shot on 2026-10-18 and covers M16-M20; the tablet sets +**predate M11** and should be regenerated on a machine with KVM before the listing goes live ([docs/STORE_LISTING.md](docs/STORE_LISTING.md)). ## Tech stack @@ -179,10 +177,10 @@ UI.** Translated UI text has to be supplied in `values-/`. - **The trash is a time window, not a bin you manage.** A delete stages the item in a hidden `.plainfiles-trash` folder on the same volume and keeps it for 24 hours; the **Trash** entry - in the overflow menu lists what is still there and puts a whole batch back. There is no - "empty it now" action — expired batches are swept the next time something is deleted — and a - name taken again in the meantime is never overwritten, so a restored item can arrive beside - the new one rather than replacing it. + in the overflow menu lists what is still there, puts a whole batch back, and offers **Empty + trash** for when the window is not what you want. Opening the trash sweeps what has expired, + so retention no longer waits for the next delete. A name taken again in the meantime is never + overwritten, so a restored item can arrive beside the new one rather than replacing it. - **Where a trashed item came from is remembered in the app's own database**, not on the volume. The staged files are the truth and the database is only an index, so the two are reconciled in both directions: an entry whose file has gone is not offered back, and a diff --git a/docs/PERMISSION_VIDEO.md b/docs/PERMISSION_VIDEO.md new file mode 100644 index 0000000..1fda41f --- /dev/null +++ b/docs/PERMISSION_VIDEO.md @@ -0,0 +1,143 @@ +# The permission-use video + +The Permissions Declaration Form asks for a link to a short video that shows +`MANAGE_EXTERNAL_STORAGE` being used — 30 seconds or fewer, with a voice-over or captions where the +UI does not make the use obvious. This file is the record of the video that answers it, and the +instructions for making another. + +## The file + +| file | length | what it is | +|---|---|---| +| `store-assets/video/permission-use.mp4` | **19.7 s** | The submission copy: 1080×2400, H.264, **no audio**, English captions burned in. The home-screen segment is deliberately cut (below) | +| `store-assets/video/permission-use-uncaptioned.mp4` | 19.7 s | The same cut without captions, for a voice-over instead | +| `store-assets/video/permission-use.srt` | — | The caption text and timings, for a re-upload or translation | + +**The file is not a link.** Play asks for a URL, so upload the MP4 — YouTube **Unlisted**, or Google +Drive shared with "anyone with the link" — and paste that URL into the form. A private video or a +sign-in-gated Drive link cannot be reviewed. Unlisted is enough; the video does not need to be +public or searchable. + +## What it shows + +| time | on screen | what it proves | +|---|---|---| +| 0:00 | The app's **Choose a folder** prompt, with **Grant full access** | The app asks for the permission rather than assuming it | +| 0:01–0:08 | Android's **All files access** screen; the toggle is off, then on | The user grants it through Android's own UI, not an in-app shortcut | +| 0:08–0:12 | *(after a deliberate cut)* Internal storage, then the **AADemo** folder | The permission is what makes a whole-volume listing possible | +| 0:12–0:14 | Documents: `budget.csv`, `itinerary.md`, `offsite-notes.txt`, `quarterly-report.pdf`, `service-agreement.docx` | The use is not media: these are files `MediaStore` does not manage | +| 0:14–0:15 | **New folder** dialog | A write is about to be attempted | +| 0:15–0:19 | **New folder** appears with the change dot; snackbar **Folder created** | The same permission is used to write, not only to read | + +**The cut at 0:08 is a privacy redaction, not an edit to the evidence.** The raw take returns to the +app's home screen between the grant and the folder listing, and that screen shows the phone's own +library counts — **6 238 images and 20 248 documents**. [STORE_LISTING](STORE_LISTING.md) forbids +publishing those counts, because they describe the owner's device rather than the app. The published +video removes the home-screen segment and resumes on the internal-storage listing, which is the +first frame that proves the grant took effect. The uncaptioned file is cut at the same two points, +so both copies tell the same story. + +The captions, in order: + +1. *Plain Files needs All files access* +2. *Android asks to allow all-file management* +3. *Switching on All files access* +4. *Granted: browsing internal storage* +5. *Documents: CSV, MD, TXT, PDF, DOCX* +6. *Writable too: a new folder is created* + +## Why the system UI is German + +The test phone (Xiaomi M2101K7AG) runs MIUI in German; the app itself is English. Changing the phone's +language to flatter a recording would not change what the permission does, and the two system strings +that matter are translated by the captions: *Zugriff auf alle Dateien* ("All files access") and +*Zugriff zum Verwalten aller Dateien zulassen* ("Allow access to manage all files"). The reviewer sees +the toggle move and the app change state, which is the evidence; the language is incidental. + +## Privacy: what is not in the video + +- The library is **generated** by `tools/make-demo-library.py`, not taken from the phone. It was + copied to `/sdcard/AADemo` so it sorts first and the recording never has to open `Download`, whose + listing on this device holds the owner's real PDFs. +- Every filename on screen — `budget.csv`, `itinerary.md`, `quarterly-report.pdf`, `AADemo` — is + synthetic. The published cut removes the home screen entirely, because its library counts + (**6 238 images, 20 248 documents**) are the owner's real totals; the uncaptioned file is cut at the + same points. What remains shows synthetic names and Android's own screens. +- Frames were inspected with a contact sheet before and after the cut, and the status bar holds + nothing but the clock, signal, battery and the screen-record indicator. + +## Re-shooting it + +The take was made on the release build, because that is the artefact the declaration is about: + +```bash +./gradlew :app:assembleRelease +adb install -r app/build/outputs/apk/release/app-release.apk +adb shell cp -r /sdcard/Download/PlainFiles-Demo /sdcard/AADemo # only if absent +adb shell appops set --uid de.softwareapp_hb.filemanager MANAGE_EXTERNAL_STORAGE deny +adb shell am force-stop de.softwareapp_hb.filemanager +adb shell am start -n de.softwareapp_hb.filemanager/.MainActivity +``` + +⚠️ **The `--uid` is the whole trap.** After Android's Settings screen grants the permission, a +package-level `appops set de.softwareapp_hb.filemanager MANAGE_EXTERNAL_STORAGE deny` leaves the +**UID mode** at `allow`, the app keeps `isExternalStorageManager() == true`, and no prompt appears. +The revoke only works with `--uid`, and `appops get` shows both lines so the state can be checked +before recording. + +Then, with the prompt verified on screen, record and drive it: + +```bash +adb shell rm -f /sdcard/permission-use.mp4 +adb shell screenrecord --size 1080x2400 --bit-rate 8000000 --time-limit 30 /sdcard/permission-use.mp4 & +sleep 1 +adb shell input tap 540 1358 # Grant full access +sleep 2.5 +# If the runtime storage dialog appears, tap ERLAUBEN at (540,1321). It does not when +# READ_EXTERNAL_STORAGE is already granted, which is the normal case on this phone. +sleep 2.5 +adb shell input tap 931 526 # All files access toggle +sleep 2 +adb shell input keyevent KEYCODE_BACK +sleep 2 +adb shell input tap 540 1600 # Internal storage +sleep 2 +adb shell input tap 540 644 # AADemo +sleep 2 +adb shell input tap 540 644 # Documents +sleep 2 +adb shell input tap 959 2146 # New folder (FAB) +sleep 1.5 +adb shell input tap 824 1368 # Create; the name is prefilled, so no keyboard appears +sleep 4 +adb shell pkill -INT screenrecord +adb pull /sdcard/permission-use.mp4 +``` + +Then cut out the home screen. In this take the last Settings frame is at **8.25 s** and the first +internal-storage frame is at **10.5 s**; re-check those two frames before reusing the numbers, because +the exact times depend on how fast the taps landed: + +```bash +ffmpeg -y -i permission-use.mp4 -filter_complex \ + "[0:v]trim=0:8.25,setpts=PTS-STARTPTS[a];[0:v]trim=10.5,setpts=PTS-STARTPTS[b];[a][b]concat=n=2:v=1[v]" \ + -map "[v]" -c:v libx264 -crf 20 -pix_fmt yuv420p -an permission-use-cut.mp4 +``` + +Do not try to record this on the AVD. Under software emulation `screenrecord` captured **one frame in +three seconds** — a three-second test produced a 1-frame, duration-less file — so the clean emulator +cannot produce video. The phone records properly; the emulator is only useful for checking the demo +library and the permission screens. + +Two details are deliberate: + +- **Never tap the name field.** On this MIUI build it raises the keyboard, the dialog shifts up, and + the next tap lands on the keyboard instead of **Create** — the first attempt created the folder but + ended on an empty screen. The prefilled *New folder* is enough to show a write. +- **The coordinates assume 1080×2400 at 440 dpi** (the Xiaomi phone). On another device, map each + screen with `adb shell uiautomator dump` and use the bounds rather than scaling these. + +The captions were burned in from six transparent PNG overlays with `ffmpeg overlay` (Pillow, DejaVu +Sans Bold 40 px, black 80 % box, bottom-centred above the snackbar). The text and timings are in +`store-assets/video/permission-use.srt`, so a re-upload can carry the same captions without burning +them in. diff --git a/docs/PLAY_RELEASE_CHECKLIST.md b/docs/PLAY_RELEASE_CHECKLIST.md index 6740b9c..60cb482 100644 --- a/docs/PLAY_RELEASE_CHECKLIST.md +++ b/docs/PLAY_RELEASE_CHECKLIST.md @@ -152,6 +152,28 @@ This is the step that can block a file manager, and the one to get right. - [ ] Keep the permission set honest: declaring a permission the app does not use is itself a policy violation. +**The declaration answer (490 / 500 characters, measured):** + +``` +Plain Files' core purpose is browsing and managing every file and folder on internal storage, SD cards and USB drives: documents, archives, APKs, hidden files and folders created by other apps. The Storage Access Framework grants one user-picked tree at a time and cannot enumerate volumes or present a whole-device hierarchy. MediaStore exposes indexed media collections, not arbitrary files or folder trees. SAF remains a fallback for a chosen folder, but cannot deliver the core purpose. +``` + +This is a **functional** argument, which is the only kind the form accepts — Google's own wording +says improving performance is not an acceptable reason, so speed is not mentioned. Each claim is true +of this build: `data/SafSource` already provides the folder-tree fallback, and the app's only +`MediaStore` use is the best-effort delete request (section 5's permission audit), so neither API is +being dismissed as unnecessary. What they cannot do is *enumerate every volume*, *present one +hierarchy spanning internal storage and removable media*, or *manage arbitrary non-media files and +folders* — which is what "file manager" means, and why the listing copy leads with it. + +If a future form allows fewer characters, cut the last sentence first: the fallback is a point of +honesty, not the justification. + +- [ ] Upload `store-assets/video/permission-use.mp4` (**19.7 s**, captions burned in) to YouTube + **Unlisted** or Drive shared with anyone who has the link, then paste that URL into the form's + video field. The repository file is not a link, and a private video cannot be reviewed. What it + shows, why the system UI is German, and how to re-shoot it: [PERMISSION_VIDEO](PERMISSION_VIDEO.md). + **Verify the shipped permission set** after every release build: ```bash @@ -197,6 +219,10 @@ Full copy and the generated graphics land in **M10** (`docs/STORE_LISTING.md`). fields needed: app name, short description, full description, app icon 512×512, feature graphic 1024×500, and the screenshots. +The per-release **What's new** field is separate from the listing and is capped at **500 characters**: +its copy, measured, is in [RELEASE_NOTES](RELEASE_NOTES.md), together with the longer announcement +for a website or GitHub release. + - [ ] Upload the screenshots from `store-assets/screenshots/`: **phone** 1080×2160 (8), **7-inch tablet** 1200×1920 (4), **10-inch tablet** 2560×1600 (4). Play wants 4+ per large-screen form factor. All three sets clear its rules — 24-bit PNG with no alpha, diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md new file mode 100644 index 0000000..732b7e1 --- /dev/null +++ b/docs/RELEASE_NOTES.md @@ -0,0 +1,146 @@ +# Release notes + +The copy a buyer reads *before* installing, as opposed to [CHANGELOG](../CHANGELOG.md), which is the +reasoning for whoever maintains the app. Two lengths live here: + +- **Play Console → What's new** — plain text, **500 characters per language**, no Markdown and no + bullets (Play renders neither), measured rather than estimated. +- **The longer announcement** — for a website, a forum post or a GitHub release, where the limit is + the reader's patience rather than a Console field. + +The source of truth for *what* changed is the changelog, and the source of truth for *what the app +claims* is the [README](../README.md) and [STORE_LISTING](STORE_LISTING.md). This file only chooses +which of those facts a new user should hear first. + +## Writing the next one + +1. **Version.** `versionName` and `versionCode` are in `app/build.gradle.kts`; the latter must be + higher than every previous upload. The first release is `1.0.0` / `1`. +2. **Changelog first.** Move the entries out of `## [Unreleased]` into a + `## [1.0.0] - YYYY-MM-DD` heading, so this file and the changelog cannot describe different + builds. +3. **Then the Play field.** Say what a user can now do that they could not before — never what the + code did. A first release describes the app instead of a diff. +4. **Measure it.** Play counts characters, line breaks included: + + ```bash + python3 -c "print(len(open('/tmp/whats-new.txt').read().strip()))" + ``` + +5. **Quote nothing that a device has not shown.** A limitation belongs in the announcement only if + it is already in the README's *Known limitations*; if it is not, the README is what needs fixing. + +--- + +## 1.0.0 — first release + +### Play Console → What's new (500 characters max) + +``` +Plain Files is here: a file manager with no ads, no account and no internet permission, so nothing leaves your device. + +Browse internal storage, SD cards and USB drives. Copy, move, rename and delete files; undo a delete, rename or move; search everywhere; find duplicates; unpack ZIP, TAR, GZ, BZ2, XZ and 7z; star the folders you use; and read images, video, PDFs and text inside the app. + +One purchase, every feature. No tracking, no subscription. +``` + +**Measured: 450 characters** (including the two blank lines between paragraphs). The remaining +headroom is deliberate: it leaves room for a sentence about a fix without rewriting the whole field. + +Why this shape: the first two sentences are the claim no competitor in the survey makes — no ads and +**no `INTERNET` permission** — and the middle one is the proof that it is still a file manager. The +verbs are the ones people use ("copy, move, rename, delete"), not the app's internal names. Nothing +here is a superlative, an emoji, or the word *free*: the app is paid, and a headline implying +otherwise is both misleading and against Play's editorial policy. + +### The longer announcement + +> Plain Files 1.0.0 is available for Android 8.0 and later. One purchase, no subscription, no ads, +> and no way for the app to send anything off the device — because it does not hold the network +> permission at all. + +#### What it does + +**Browsing.** Internal storage, SD cards and USB drives side by side. List or grid, sorted by +name — `file2` before `file10` — size, date or type; hidden files on request; breadcrumbs; and a +search that either stays in the folder you are in or looks everywhere through an on-device index +which says how current its answer is. + +**Moving files around.** Copy, move, rename and delete one item or many. **Copy to…** and **Move +to…** open a folder picker — volumes first, then one level at a time — and start the transfer into +that folder without taking you out of the one you are standing in. A finished copy says +*Copied to folder* and offers **Show**. A name that already exists asks what Paste asks instead of +overwriting, and a batch that cannot succeed is refused as a whole rather than performed until it +hits the problem. The rows an operation produced carry a dot until you next open that folder, so +"what did that 4 GB copy do?" has an answer even when the destination was elsewhere. + +**Undo, and a trash that explains itself.** A delete is a move into a hidden `.plainfiles-trash` +folder on the same volume, kept for **24 hours**; the Trash screen states that window, offers +**Empty trash** behind one question, and sweeps expired batches when you open it. Rename and move +offer undo as well. + +**Renaming a batch.** Select several files and Rename opens a pattern rather than the single-file +dialog: one name, the number to start at, and a live preview of the names it will produce. The +numbering **continues the folder's own series** — `Holiday (1)` and `Holiday (2)` already there make +the next batch start at 3 — because a batch rename that always starts at 1 is useless the second +time it is used. A name in the way blocks the batch and is named as it is typed. + +**Finding things.** The duplicate finder compares size, then the head of every candidate, then the +survivors in full — never a name or a date. The storage-usage view measures every folder and ranks +it largest first, so the folder eating the card is the one at the top. + +**Looking at files.** Image, text, PDF, media and archive viewers, and an APK's own details read +without installing it. Thumbnails show the picture, and a **video shows a frame of itself** rather +than a film icon. An archive can be read without extracting it first, with entry names checked +against zip-slip. + +**Archives.** Create ZIP; extract ZIP, TAR, TAR.GZ, TAR.BZ2, TAR.XZ, GZ, BZ2, XZ and 7z. When +compressing, you choose the name and whether to compress at all: for copying a collection to a +computer, storing the files as they are takes one pass and is still an ordinary archive. + +**Working with other apps.** Plain Files can receive a share from anywhere and appear in another +app's file chooser. Neither costs a permission. + +**Tablets and large windows.** At expanded width the screen splits into two panes: the folder list +on the left, and the contents — or a preview and the details — of the folder or file you pick on the +right. + +**Shortcuts.** Long-press the app icon for **Images**, **Recent** and **Trash**. + +#### What it deliberately does not do + +- **No cloud storage, network shares, FTP/SFTP/WebDAV or web server.** Every one of those needs + `INTERNET`, which is the single permission this app refuses to hold. That is the product, not a + gap in the schedule. +- **RAR is not supported**, because it needs native code this app does not ship — and it says so + rather than failing with a strange error. 7z can be read but not created. +- **Extraction does not restore timestamps or permissions**, and archive passwords are not + supported. +- **Cancelling happens between entries**, so one very large file finishes before a stop is honoured. +- **English is the only language so far.** Every string is in `strings.xml`, so a translation is a + file rather than a refactor. +- **Android TV is specified but not shipped.** Its acceptance needs a TV device pass this machine + cannot provide, and a half-tested TV build reviews worse than none. +- **A library is not restored across process death.** Images or Recent returns to the root screen + after Android kills the process, because a library has no path to persist. + +#### Privacy, in one paragraph + +The app declares no `INTERNET` permission, contains no advertising or analytics SDK, and has no +account or sign-in. It keeps three things on the device — an index of files for search, an index of +staged deletes for the trash, and the list of starred folders — and none of them leaves it. The +permission set and why "All files access" is the one sensitive declaration are in the +[store listing](STORE_LISTING.md); the policy text is [PRIVACY.md](../PRIVACY.md). + +#### Price + +**€4.49**, with a **€2.29 launch price for the first 14 days**. One purchase, every feature; no +subscription and nothing to buy inside the app. There is no Billing client in the build — the price +lives on the listing — which is part of how "nothing leaves the device" stays true of the shipped +artefact rather than only of the source. + +#### What comes next + +Android TV is specified and waits for a device; the post-1.0 queue and the deliberate deferrals are +in [ROADMAP](ROADMAP.md). What will not change is the permission set: features arrive without a new +permission, or they do not arrive. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 9c0583c..a4ed33f 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,8 +1,8 @@ # Roadmap -File Manager is built against a fixed milestone plan (**M0–M15**). This file is the -single source of truth for what is done and what remains. **M16** (Copy to… / Move to…) has since been -built and is specified below; the queue records what is left. +File Manager is built against a fixed milestone plan. **M0–M20 are built**; **M21** (Android TV) is +specified and deliberately waits for a device. This file is the single source of truth for what is +done and what remains, and the queue records what is left. Legend: ✅ done · 🚧 in progress · ⏳ not started · 💡 candidate (proposed, not scheduled) diff --git a/store-assets/video/permission-use-uncaptioned.mp4 b/store-assets/video/permission-use-uncaptioned.mp4 new file mode 100644 index 0000000..41e9097 Binary files /dev/null and b/store-assets/video/permission-use-uncaptioned.mp4 differ diff --git a/store-assets/video/permission-use.mp4 b/store-assets/video/permission-use.mp4 new file mode 100644 index 0000000..45123ab Binary files /dev/null and b/store-assets/video/permission-use.mp4 differ diff --git a/store-assets/video/permission-use.srt b/store-assets/video/permission-use.srt new file mode 100644 index 0000000..df2462d --- /dev/null +++ b/store-assets/video/permission-use.srt @@ -0,0 +1,23 @@ +1 +00:00:00,200 --> 00:00:03,400 +Plain Files needs All files access + +2 +00:00:03,400 --> 00:00:06,200 +Android asks to allow all-file management + +3 +00:00:06,200 --> 00:00:08,250 +Switching on All files access + +4 +00:00:08,250 --> 00:00:11,750 +Granted: browsing internal storage + +5 +00:00:11,750 --> 00:00:13,750 +Documents: CSV, MD, TXT, PDF, DOCX + +6 +00:00:13,750 --> 00:00:19,600 +Writable too: a new folder is created