Show the dataSync copy in a video, and answer its declaration

Play asks separately about FOREGROUND_SERVICE_DATA_SYNC: which use case, what
the service does, what the user loses if the task is deferred or interrupted,
and a video of the user triggering the work. The answers are now in section 5
of the release checklist, and the video is recorded.

The use case is Local processing -> Importing, exporting. Play's own chart
describes it as moving files from an SD card locally, which is what Paste and
Copy to... / Move to... do. The description (482/500 characters) names every
OperationKind - COPY, MOVE, DELETE, COMPRESS, EXTRACT - and states that the app
has no INTERNET permission, so network processing, backup/restore and media
transcoding are explicitly not ticked.

The video (18.0 s, captions burned in) shows a 1.8 GB synthetic folder copied to
another local folder: the selection and Copy, Paste here, the "Copying files"
notification with its progress bar, and the completed folder. It is cut from a
35.2 s raw take to remove the two waits; the target listing stays empty until
the copy completes, so those frames are progress rather than failure. The shade
was cleared of the phone's weather and Play cards first, leaving only Android's
generic USB cards, and every filename is synthetic.

Two traps are written down: do not press Up after Copy, because the selection
is already in AADemo and an extra Up makes Paste target AADemo ("Items already
exist here"); and open the shade only after cmd notification list shows the
app's notification, or the video shows no evidence of the service.

Verified: ./tools/verify.sh green, the code map current, links resolve, both
videos decode as H.264 1080x2400 with no audio, and the SRT has five cues.
This commit is contained in:
2026-10-18 14:28:19 +02:00
parent 9b8a32fbd6
commit f9305d1070
7 changed files with 206 additions and 0 deletions
+1
View File
@@ -120,6 +120,7 @@ comment. `app/src/test/java/…/ProjectInvariantsTest.kt` holds the source-level
| `docs/PLAY_RELEASE_CHECKLIST.md` | Release only — work it in order | | `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/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/PERMISSION_VIDEO.md` | The declaration's 23-second video: what it shows, and how to re-shoot it |
| `docs/FGS_VIDEO.md` | The foreground-service video: the local copy, what was cut, 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 | | `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 | | `PRIVACY.md` | The policy text the store listing points at |
+13
View File
@@ -48,6 +48,19 @@ upload needs a **higher `versionCode`** than every previous upload.
package-level `appops set … deny` leaves the UID mode allowed, so only package-level `appops set … deny` leaves the UID mode allowed, so only
`appops set --uid … deny` brings the prompt back. `appops set --uid … deny` brings the prompt back.
- **The `dataSync` foreground-service declaration is answered before the Console asks.** Section 5 of
[PLAY_RELEASE_CHECKLIST](docs/PLAY_RELEASE_CHECKLIST.md) now records the
`FOREGROUND_SERVICE_DATA_SYNC` answers: the use case is **Local processing → Importing, exporting**
(Play's own example is moving files from an SD card), the **482-character** functionality
description, and the deferred and interrupted impact. Every `OperationKind` is local — `COPY`,
`MOVE`, `DELETE`, `COMPRESS`, `EXTRACT` — and the app has no `INTERNET` permission, so network
processing, backup/restore and media transcoding are explicitly not ticked. It also records that
the required video is **not** the All files access one: [FGS_VIDEO](docs/FGS_VIDEO.md) holds an
**18.0 s** captioned take of the local copy — selection, Copy, Paste here, the notification with
its progress bar, and the completed folder — cut from a 35.2 s raw recording to remove the two
waits, with every filename synthetic. The traps are written down too: no Up after Copy, and open
the shade only once the notification exists.
- **A dot marks what an operation just produced.** Copy something, extract an archive, rename a file or make a - **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 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 copy do?" has an answer, including when the copy went to a folder you were not looking at. The mark lives in
+136
View File
@@ -0,0 +1,136 @@
# The foreground-service video
The Play Console's foreground-service declaration for `FOREGROUND_SERVICE_DATA_SYNC` requires a video
of the user *triggering* the local import/export, not just a description. This is that video, and the
record of how it was made. The All files access declaration has its own video and page:
[PERMISSION_VIDEO](PERMISSION_VIDEO.md).
## The file
| file | length | what it is |
|---|---|---|
| `store-assets/video/fgs-import-export.mp4` | **18.0 s** | The submission copy: 1080×2400, H.264, **no audio**, English captions burned in |
| `store-assets/video/fgs-import-export-uncaptioned.mp4` | 16.8 s | The same cut without captions, for a voice-over |
| `store-assets/video/fgs-import-export.srt` | — | The five captions and their timings |
**The file is not a link.** Upload it to YouTube **Unlisted**, or Drive shared with "anyone with the
link", and paste that URL into the foreground-service declaration. A private upload cannot be
reviewed.
## What it shows
| time | on screen | what it proves |
|---|---|---|
| 0:00 | `AADemo`, with `Source` selected and **Copy** in the action bar | The user starts a local import/export |
| 0:03 | `Target`, then the overflow's **Paste here** | The user chooses the local destination and triggers it |
| 0:10 | The notification shade: **Copying files** with a moving progress bar | The `dataSync` foreground service is doing the work and reporting it |
| 0:12 | `Target` with `Source` and the snackbar **Copied 121 items** | The operation completed at the destination |
The captions, in order:
1. *Copy a 1.8 GB folder to another folder*
2. *Paste here starts a local copy*
3. *The copy runs in the dataSync service*
4. *The ongoing notification reports progress*
5. *Done: the folder is at the destination*
## What was cut, and why
The raw take was **35.2 s**; the published copy is three pieces of it:
| kept | what it is |
|---|---|
| 0–10 s | Select `Source`, Copy, open `Target`, Paste here |
| 17–19.5 s | The shade with **Copying files** and its progress bar |
| 22–27 s | The completed `Target` and its snackbar |
The two gaps are elapsed time, not evidence: the wait between Paste and the notification appearing,
and the wait between the shade closing and the listing refreshing. **The app's target listing stays
empty until the copy finishes and then refreshes itself**, so the empty frames are the operation in
progress, not a failure. The notification and the result are from the same take.
Nothing else is edited. No home screen, launcher or library view appears — the recording starts inside
`AADemo`. The shade was cleared of the phone's weather and Play cards before the take; what remains
besides the app's notification is Android's generic USB debugging/transfer pair. The quick-settings
panel shows only icons, battery and data totals; the Wi-Fi name exists in the accessibility tree but
is not drawn on screen.
Every filename is synthetic: the source is `AADemo/Source/file001.bin` … `file120.bin`, made from
`/dev/zero`, and the destination is `AADemo/Target`.
## Re-shooting it
Create the fixture first — 120 files of 16 MB, so the copy lasts long enough to read the notification:
```bash
adb shell 'rm -rf /sdcard/AADemo/Target; mkdir -p /sdcard/AADemo/Source /sdcard/AADemo/Target
for i in $(seq -w 1 120); do
dd if=/dev/zero of=/sdcard/AADemo/Source/file$i.bin bs=1M count=16 2>/dev/null
done'
```
Clear the notification shade before recording: open it, tap **Clear all**, and check with
`adb shell cmd notification list`. The phone's own weather, Play and messaging cards must not appear
in a video that will be published; USB debugging/transfer cards are generic and may stay.
Open the app on **Internal storage → AADemo**, long-press `Source`, and verify the bar says
**1 item** with **Copy** before recording. Then:
```bash
adb shell rm -f /sdcard/fgs.mp4
adb shell screenrecord --size 1080x2400 --bit-rate 8000000 --time-limit 50 /sdcard/fgs.mp4 &
sleep 1.5
adb shell input tap 231 2155 # Copy in the selection bar
sleep 1.5
adb shell input tap 540 644 # Target — already inside AADemo
sleep 2
adb shell input tap 1003 181 # overflow
sleep 1.5
adb shell input tap 864 335 # Paste here
# The notification exists only after Paste. Wait for it, or the shade shows only USB cards.
for i in $(seq 1 16); do
adb shell cmd notification list | grep -q 'de.softwareapp_hb.filemanager' && break
sleep 0.5
done
adb shell cmd statusbar expand-notifications
sleep 4
adb shell cmd statusbar collapse
sleep 18 # the copy must finish and the listing refresh
adb shell pkill -INT screenrecord
adb pull /sdcard/fgs.mp4
```
Cut the two waits, keeping the trigger, the notification and the result:
```bash
ffmpeg -y -i fgs.mp4 -filter_complex \
"[0:v]trim=0:10,setpts=PTS-STARTPTS[a];[0:v]trim=17:19.5,setpts=PTS-STARTPTS[b];[0:v]trim=22:27,setpts=PTS-STARTPTS[c];[a][b][c]concat=n=3:v=1[v]" \
-map "[v]" -c:v libx264 -crf 20 -pix_fmt yuv420p -an fgs-cut.mp4
```
The times are this take's; check the frames before reusing them.
Two traps cost takes here:
- **Do not press Up after Copy.** The selection is made *in* `AADemo`; Copy clears the bar and
`Target` is the next row. An extra Up goes to the card root, the following taps open `AADemo`
again, and Paste then targets `AADemo` — which already holds `Source`, so the app correctly asks
**“Items already exist here”**. The first attempt did exactly that.
- **Open the shade only once the notification exists.** Opening it straight after Paste can beat the
service's first post and shows nothing but Android's USB cards — a video with the trigger and the
result but no evidence of the service. The `cmd notification list` loop above is the guard.
Also expect the target listing to stay **empty** until the copy completes and the ViewModel reloads
it. That is the operation still running, not a missed Paste; `du -sh /sdcard/AADemo/Target` is the
independent check.
The captions are five PNG overlays at **y = 1900**, not the 2130 used by the all-files video: the
selection bar sits at 2133–2177 on this phone, and a caption there hides the Copy button — the very
action the video exists to show.
## Uploading it
Upload `store-assets/video/fgs-import-export.mp4` to YouTube **Unlisted** or Drive with link sharing,
and paste the URL into the **foreground-service** declaration. This is a different field from the
All files access video: the permission video shows browsing a whole volume, this one shows the local
copy that the `dataSync` service keeps alive.
+37
View File
@@ -174,6 +174,43 @@ honesty, not the justification.
video field. The repository file is not a link, and a private video cannot be reviewed. What it 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). shows, why the system UI is German, and how to re-shoot it: [PERMISSION_VIDEO](PERMISSION_VIDEO.md).
### The foreground-service declaration (`dataSync`)
`FileOpService` is a **`dataSync`** foreground service: it keeps a copy, move, delete (into the trash),
compress or extract alive while the user is in another app, and mirrors the engine's progress into an
ongoing, silent notification. It runs no network code and the app has no `INTERNET` permission. Play
asks about this separately from All files access.
- **Use case: Local processing → Importing, exporting.** Play's own chart describes the use case as
*"Includes migration of files from SD card. Show the user manually triggering an import or export
of files locally (for example, as moving files from an SD card)"* — which is what **Paste** and
**Copy to… / Move to…** do. Do **not** tick network processing, backing up/restoring, media
transcoding or "Other tasks": every `OperationKind` is local — `COPY`, `MOVE`, `DELETE`, `COMPRESS`,
`EXTRACT` — and none of them touches a network.
- **Functionality description (482 / 500 characters, measured):**
```
Plain Files is a file manager. Its dataSync service runs user-initiated local file operations: copying and moving files and folders between folders, internal storage, SD cards and USB drives; deleting into a 24-hour trash; creating ZIPs; extracting ZIP, TAR, GZ, BZ2, XZ and 7z archives. It keeps a long operation alive while the user switches apps and posts an ongoing, silent progress notification until the work ends. All operations are local; the app has no INTERNET permission.
```
- **If the task is deferred** (*"does not start immediately"*): *"If deferred, a copy, move, delete,
compress or extract the user just confirmed would not start while Plain Files is in the background:
no progress notification and no visible change until the app returns. A large operation could look
as if it had silently failed, and the user might start it again."*
- **If the task is interrupted** (*"paused and/or restarted"*): *"If interrupted, the work stops.
Items already completed remain, but a file being copied or extracted can be left partly written at
the destination, and the user has to start the operation again. Nothing is overwritten without
asking, and deletes are staged in the 24-hour trash, so completed deletes remain recoverable."*
- [ ] **Upload the foreground-service video.** `store-assets/video/fgs-import-export.mp4` (**18.0 s**,
captions burned in) to YouTube **Unlisted** or Drive with link sharing, then paste that URL into
the foreground-service declaration. It shows the selection, **Copy**, **Paste here**, the
**Copying files** notification with its progress bar, and the completed folder. It is a
different field from the All files access video. What was cut, the privacy checks, and how to
re-shoot it: [FGS_VIDEO](FGS_VIDEO.md).
**Verify the shipped permission set** after every release build: **Verify the shipped permission set** after every release build:
```bash ```bash
Binary file not shown.
Binary file not shown.
+19
View File
@@ -0,0 +1,19 @@
1
00:00:00,200 --> 00:00:03,000
Copy a 1.8 GB folder to another folder
2
00:00:03,000 --> 00:00:06,500
Paste here starts a local copy
3
00:00:06,500 --> 00:00:10,000
The copy runs in the dataSync service
4
00:00:10,000 --> 00:00:12,500
The ongoing notification reports progress
5
00:00:12,500 --> 00:00:17,900
Done: the folder is at the destination