Skip to content

Commit 224c8fd

Browse files
committed
Ask the scope table, not the uid map, whether an install matters
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.
1 parent d2c101a commit 224c8fd

2 files changed

Lines changed: 49 additions & 3 deletions

File tree

daemon/src/main/kotlin/org/matrix/vector/daemon/VectorService.kt

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,16 @@ object VectorService : IVectorDaemon.Stub() {
312312
if (isRemovedForAllUsers && ModuleDatabase.removeModule(moduleName)) {
313313
// If it was in our DB and we successfully removed it, we treat it as an Xposed module.
314314
isXposedModule = true
315+
} else if (ModuleDatabase.isEnabledScopeTarget(moduleName)) {
316+
// A target's scope rows outlive it deliberately — they are what puts the module back
317+
// when the app returns — but the entry the cache derived from them must not, and this
318+
// branch asked for nothing. What cleaned it up was ACTION_UID_REMOVED, further down,
319+
// and only ever incidentally: it fires when the *uid* is retired, which is neither
320+
// this event nor guaranteed to follow it. A package that shares a uid with another
321+
// retires none, and a daemon not running to hear the one that is sent never learns of
322+
// it. The entry left behind is keyed by a uid that no longer names this app, and
323+
// Android does hand a freed app id to the next installer that asks.
324+
ConfigCache.requestCacheUpdate()
315325
}
316326
}
317327
}
@@ -323,9 +333,23 @@ object VectorService : IVectorDaemon.Stub() {
323333
ModuleDatabase.updateModuleApkPath(
324334
moduleName, ConfigCache.getModuleApkPath(appInfo), false)
325335
} else {
326-
if (ConfigCache.state.scopes.keys.any { it.uid == uid }) {
327-
// If not a module, but it's an app that was previously a "scope" (target)
328-
// for a module, we need to refresh the cache.
336+
// If not a module, but it's an app some module targets, the cache has to be rebuilt so
337+
// that the target is resolved again.
338+
//
339+
// The configuration is asked first, and by name, because the cache alone gets the one
340+
// case that matters most wrong. Its scopes are keyed by uid: an app that is uninstalled
341+
// and installed again comes back under a *new* uid, and the entry under the old one went
342+
// when ACTION_UID_REMOVED rebuilt the cache — so no key matched, no rebuild was asked
343+
// for, and the app was left out of the scope map it is still configured to be in. The
344+
// row was never deleted, so the manager went on showing the target ticked, correctly,
345+
// beside an app nothing was being loaded into; there was no difference to apply and
346+
// therefore no way to put it right from the manager at all. It stayed that way until
347+
// something unrelated rebuilt the cache — any scope edit, or the next boot.
348+
//
349+
// The uid test is kept behind it for the rows no scope table holds: a module in its own
350+
// scope, and the self-scope a legacy module gets derived rather than stored.
351+
if ((moduleName != null && ModuleDatabase.isEnabledScopeTarget(moduleName)) ||
352+
ConfigCache.state.scopes.keys.any { it.uid == uid }) {
329353
ConfigCache.requestCacheUpdate()
330354
}
331355

daemon/src/main/kotlin/org/matrix/vector/daemon/data/ModuleDatabase.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,28 @@ object ModuleDatabase {
143143
return rows
144144
}
145145

146+
/**
147+
* Whether any enabled module names [appPackage] as a target, in any user.
148+
*
149+
* Asked of the configuration rather than of [ConfigCache], because the cache cannot answer it.
150+
* Its scope map is keyed by uid, and a package that was uninstalled and installed again comes
151+
* back under a *new* one — while the row here, keyed by name, has been waiting for it the whole
152+
* time. Matching the cache by uid therefore said "not a target" about the one case that most
153+
* needs a rebuild, and the app stayed unhooked until something unrelated rebuilt the cache.
154+
*/
155+
fun isEnabledScopeTarget(appPackage: String): Boolean =
156+
dbHelper.readableDatabase
157+
.query(
158+
"scope INNER JOIN modules ON scope.mid = modules.mid",
159+
arrayOf("1"),
160+
"app_pkg_name = ? AND enabled = 1",
161+
arrayOf(appPackage),
162+
null,
163+
null,
164+
null,
165+
"1")
166+
.use { it.moveToFirst() }
167+
146168
/** Enabled modules scoped to the system framework, with the path last resolved for each. */
147169
fun systemServerModuleRows(): List<EnabledModuleRow> {
148170
val rows = mutableListOf<EnabledModuleRow>()

0 commit comments

Comments
 (0)