Skip to content

Lower minSdk to 28, fix two real Android 9 bugs - #4

Merged
munzzyy merged 13 commits into
mainfrom
android-9
Oct 1, 2026
Merged

munzzyy merged 13 commits into
mainfrom
android-9

Conversation

@munzzyy

@munzzyy munzzyy commented Oct 1, 2026

Copy link
Copy Markdown
Owner

A user on Android 9 asked for Tern on Starling's issue tracker (munzzyy/starling#6). This brings minSdk down from 29 to 28.

What changes for Android 9:

Downloads. Writing to Download/Tern without a picker needs MediaStore.Downloads, which only exists from Android 10. On 9, saving a release's own file opens the system file picker. A kept export with no folder chosen says plainly that it needs Android 10 instead of crashing. The two existing picker-based flows (manual export, folder-based kept export) already worked on 9 unchanged.

Four API-level-only fixes with no behavior change on newer Android: Dhizuku's binder naming, the transfer notification's foreground service type, the quick settings tile's subtitle, and never aging out an install session where Android 9 answers neither timestamp field.

Two real bugs. Found only once Android 9 actually ran the install flow. Android 9's install confirmation carries a different system action (CONFIRM_PERMISSIONS, not CONFIRM_INSTALL) and was silently never shown. A file whose own manifest needs a newer Android was blamed as unreadable instead of named for what it needed.

One test-only fix. Two handoff tests launch MainActivity directly instead of through the usual test setup, and Instrumentation's own launch wait never returns for that on this device. Confirmed with a thread dump: the activity really reaches RESUMED, main is idle, the wait is stuck on Android's own monitor. Skipped there with the reason on record.

Proven on a real Android 9 emulator, API 28, x86_64:

Full gate green: core:test, app:testDebugUnitTest, assembleRelease, lintRelease, check-apk.sh, check-java-api.sh, check-network-doors.sh, check-handoff-page.sh, check-site.js, strings.py (the one new file is English-only on purpose, noted below).

Device suite: 282 passed, 0 failed, 48 skipped. The skips are version-appropriate: certificate transparency, Dhizuku's device-owner tests that need a second installed app the test runner can't fake, and the two handoff tests above. Two flakes under full-suite load (a Compose layout race, a notification-cleanup timing race) did not reproduce when the same tests ran alone.

By hand: added magpie by its GitHub address, hit Android's own install permission prompt, got the real system confirmation dialog, installed it, exported the list through the file picker, and imported it back.

New strings, English only this round, listed per instructions rather than translated: engine_picked_place in app/src/main/res/values/strings_android9.xml.

MediaStore.Downloads, which Download/Tern is built on, exists only from
Android 10 on. Below that, saving a release's file or a kept export
with no folder chosen now says plainly that it needs Android 10 instead
of crashing, and the Save button on a release's own page opens the
system file picker instead, so a person who is right there can still
choose where the file goes. New strings for this live in their own
file, strings_android9.xml, left untranslated for now.
Binder(String) needs Android 10. The descriptor it would have carried
is only ever read back by overriding getInterfaceDescriptor, which
works on every version, so that is what the two binders Dhizuku talks
through do now instead.
startForeground with a service type needs Android 10. Below that the
two-argument form says the same thing to the person, a notification
while a download runs, without naming a type Android 9 has never heard
of.
Tile.setSubtitle needs Android 10. Below that the tile's label carries
the same words a subtitle would have, so the count of updates still
shows, just not next to a separate "Check" line.
PackageInstaller.SessionInfo answers neither createdMillis nor
updatedMillis there, both of which need 10 or later. A session on
Android 9 is now simply never abandoned by age, rather than crashing
the whole cleanup pass.
Instrumentation.startActivitySync never returned on a real Android 9
device for these two tests, confirmed from a thread dump: the activity
really reached RESUMED, main was idle, but the launch monitor was never
told. Launching into a cleared, explicit task did not change that
either, so the two tests that need a real screen are skipped there with
the reason on record, and every other handoff test still runs.
Two real bugs, both found only once Android 9 ran the install flow for
the first time. First: where Android 10 and later hand back a confirm
intent with the action CONFIRM_INSTALL, Android 9 hands back one with
CONFIRM_PERMISSIONS instead; Tern only ever recognised the first, so the
confirmation was silently never shown there, not even as a notification.
Second: a file whose own manifest asks for a newer Android than the
device has makes Android's own parser return nothing at all, which Tern
read as a corrupt file ("Android could not read this file") instead of
naming the real reason. The file's own manifest, which Tern reads apart
from Android, already has the minSdk to say so correctly.
Proven on a real Android 9 emulator: the full device suite, and by hand
adding an app, installing it with the system's own confirmation,
exporting the list through a picker and importing it back.
The same minSdk check in Installs.kt belongs here too: when Android's
own parser refuses an archive outright, that is usually because the
file's own manifest already asks for a newer Android than this device
has, which Tern's own manifest reading already knows.
Google's last fixes for Android 9 came out in January 2022. Tern checks files, not Android, so it says so once, as the other apps do. The Persian strings from this round had lost the zero-width non-joiners Persian needs between a word and its prefix or plural; they are back.
@munzzyy
munzzyy merged commit 265a64f into main Oct 1, 2026
2 checks passed
@munzzyy
munzzyy deleted the android-9 branch October 1, 2026 01:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant