Make the Android 13+ pass, and fix what it found
The phone runs Android 12, so the questions that matter most for a release were exactly the ones it could not answer. Installed an API 35 AVD (sdkmanager was already present and dl.google.com reachable) rather than waiting for a public test track to report back. It found two user-visible defects, neither of which any test could have caught. On Android 13+ the app asked for no runtime permission at all, so the progress notification was dead. StorageAccessPolicy.missingPermissions returned an empty list from API 33 on the reasoning that "all files access" is a special permission and browsing therefore needs no runtime grant — true as far as browsing goes, but the early return also dropped POST_NOTIFICATIONS, and nothing else in the app requested it. startForeground still ran, so nothing crashed and all 203 tests passed, while dumpsys showed granted=false and importance=NONE. Settings could not fix that either, because Android will not let a user grant a permission the app has never asked for. The notification grant is now requested alongside the storage grant, which is the only moment Android accepts it. The four READ_MEDIA_* declarations were in the same position, and the manifest comment claiming they back thumbnails and viewers on Android 13+ was wrong. Coil loads both from a real filesystem path — the code comments already say "all-files mode only" — and the one MediaStore use is a best-effort delete request that "all files access" already covers. They were never requested, so they were never granted, so nothing could have depended on them. Removed, which also settles the checklist rule that declaring an unused permission is itself a policy violation. The selection bar drew its last three actions off the screen on a phone. With one archive selected it holds ten icon buttons, ~480 dp at the 48 dp minimum touch size against the 411 dp a phone has, and BottomAppBar is a plain Row that does not scroll. Compose laid the surplus out past the right edge: measured on a 1080 px screen the row ran Copy(167) to Compress(923-980, itself squeezed to 57 px) and stopped, so Extract here, Select all and Clear selection were unreachable, while the same selection in a 2400 px landscape window showed all ten. The logic was never wrong — ArchiveFormats.isExtractable had judged the .zip correctly — the app just looked like it could not extract. The action row now scrolls, verified by swiping it in portrait and seeing all ten appear. Verified on the AVD rather than only built: the notification dialog now appears and importance goes NONE -> DEFAULT, a real 220 MB copy posts notification 1001 on channel file-operations with ONGOING_EVENT|FOREGROUND_SERVICE and tears it down plus stops the service when done, edge-to-edge is correct under API 35s enforcement, predictive back returns from a folder and keeps the activity resumed, and Compress to ZIP produced an archive the host unzip read back with the right relative paths and contents. 204 JVM tests, lint clean, release artefacts re-measured.
This commit is contained in:
@@ -12,6 +12,40 @@ upload needs a **higher `versionCode`** than every previous upload.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **The progress notification never appeared on Android 13 or newer, and five permissions were
|
||||
declared that the app never used.** `StorageAccessPolicy.missingPermissions` returned an empty
|
||||
list from API 33 on the reasoning that "all files access" is a special permission rather than
|
||||
a runtime one, so browsing needs no runtime grant. That part is true — but the early return
|
||||
also silently dropped `POST_NOTIFICATIONS` from the set, and nothing else in the app ever
|
||||
requested it. On Android 13+ the grant could therefore never be given at all: `startForeground`
|
||||
still ran, so nothing crashed and every test passed, while the system dropped the notification
|
||||
(`importance=NONE`) and even Settings could not turn it on, because Android will not let a user
|
||||
grant a permission the app has never asked for. The four `READ_MEDIA_*` declarations were in
|
||||
the same position — declared for "thumbnails and the built-in viewers on Android 13+", never
|
||||
requested, and never needed: Coil loads thumbnails and the viewers from a real filesystem path,
|
||||
and the only `MediaStore` use is the best-effort delete request, which "all files access" already
|
||||
covers. Those declarations are removed, and the notification grant is now requested alongside
|
||||
the storage grant — the moment the user commits to the app's full mode, and the only moment
|
||||
Android accepts. Verified on an Android 15 emulator: the dialog now appears, the app's
|
||||
notification importance goes `NONE → DEFAULT`, and the progress notification is posted, kept up
|
||||
to date and torn down around a real 220 MB copy.
|
||||
|
||||
- **The selection bar drew its last three actions off the screen on a phone, where nothing could
|
||||
reach them.** With a single archive selected the bar holds ten `IconButton`s — Copy, Cut, Rename,
|
||||
Details, Share, Delete, Compress, Extract, Select all and Clear selection — and at the 48 dp
|
||||
minimum touch size that needs roughly 480 dp plus the item-count label, against the 411 dp a
|
||||
normal phone has. `BottomAppBar` is a plain `Row` and does not scroll, so Compose laid the
|
||||
surplus out past the right edge and clipped it: measured on a 1080 px (411 dp) screen the row
|
||||
ran Copy(167)…Compress(923–980, itself squeezed from 63 px to 57) and then simply stopped, while
|
||||
the same selection in a 2400 px landscape window showed all ten. `Extract here`, `Select all`
|
||||
and `Clear selection` were therefore unreachable on the form factor most people use, which made
|
||||
the app's own archive feature look like it did not exist — and the extract path itself was fine,
|
||||
`ArchiveFormats.isExtractable` had judged the `.zip` correctly all along. The action row now
|
||||
scrolls, so nothing can be clipped at any width or font scale. Found by actually driving compress
|
||||
and extract from the UI on the API 35 emulator instead of trusting the scripted long-press
|
||||
gesture that had already proved flaky; the note that this path "is still worth a manual pass"
|
||||
turned out to be exactly right.
|
||||
|
||||
- **`.xz` extraction would have crashed on a real device.** Commons Compress declares its
|
||||
LZMA/LZMA2 support — the `.xz` compressor and the 7z format — against `org.tukaani:xz` as
|
||||
an **optional** dependency. Optional means it is not pulled in transitively, so nothing
|
||||
|
||||
Reference in New Issue
Block a user