Ask the scope table, not the uid map, whether an install matters - #931
Merged
Conversation
A scope row names a package; the cache derived from it is keyed by uid. Uninstalling a target and installing it again crosses that gap: the app returns under a new uid, the entry under the old one went when ACTION_UID_REMOVED rebuilt the cache, and the install handler — which recognised a target by looking its uid up in that same cache — matched nothing and asked for no rebuild. The row it is still configured under sat there unread. Which left the configuration correct and inert. The manager reads the table, so it went on showing the target ticked, truthfully; the draft and the saved set agreed, so no apply bar appeared and there was no difference to write. Nothing on that screen could put it right. It took a scope edit on some other module, or the next boot, to rebuild the map and bring the app back into a scope it had never left. Removal now asks for its own rebuild for the same reason it should always have: the entry was cleaned up only incidentally, by the ACTION_UID_REMOVED that follows a uid actually being retired — which a package sharing one never is, and which a daemon that was not running never hears. What is left behind is keyed by a uid that no longer names the app, and Android hands a freed app id to the next installer to ask. The uid test stays behind the new one for the scope rows no table holds: a module in its own scope, and the self-scope derived for a legacy one.
The behaviour the previous commit restores was never stated anywhere: a scope row names a package and a user and stores no uid, so it outlives the application it points at, and installing that application again puts the module back. It is worth saying out loud, because the alternative reading — that an uninstall retires the configuration the way Android retires a runtime permission — is a reasonable thing to expect, and the cost of the choice is real: the name is the whole identity, so a different build that later claims it inherits the scope. The daemon README gains the rule, what follows from it, the four paths that do delete a row, and the reason package events have to consult the table by name. The schema carries it beside the primary key that decides it. ConfigCache notes the asymmetry where it deletes a *module* that no user holds, which is the one place the two rules sit close enough to be confused for each other. The scope editor says why it seeds its draft from the whole saved set: rows it cannot draw are exactly the rows of an absent target, and writing a difference rather than a replacement is what carries them through an apply.
JingMatrix
force-pushed
the
scope-target-reinstall
branch
from
August 22, 2026 12:19
fba0bfa to
406c7d0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A scope row names a package; the map the injector reads is keyed by uid. Uninstalling a target and installing it again crosses that gap. The app returns under a new uid, the entry under the old one went with
ACTION_UID_REMOVED, and the install handler looked a target up by uid in that same cache — so it matched nothing, asked for no rebuild, and the row it is still configured under sat unread.That leaves the configuration correct and inert. The manager reads the table, so it goes on showing the target ticked, truthfully; draft and saved agree, so no apply bar appears and nothing on that screen can put it right. It takes a scope edit elsewhere, or the next boot. A scoped app installed into a second user missed the same way.
Asking the scope table by name answers both. Removal now asks for its own rebuild too: that cleanup was always incidental, from the
ACTION_UID_REMOVEDa retired uid sends — which a shared one never does, and which a daemon that was down never hears. What it leaves behind is keyed by a uid that no longer names the app, and Android reuses freed app ids.