Skip to content

Commit e3b15cf

Browse files
committed
Add per-app ART inline hook compatibility mode
Adds a per-package switch to disable Vector's ART inline hooks in selected apps for compatibility. Long-pressing an app in a module's scope now offers the toggle in the package action sheet, instead of a dedicated screen. The native routine restores libart.so's file-backed executable image and the process's pre-injection modifications after framework bootstrap, leaving LSPlant/Dobby metadata intact; it applies to the app the next time it starts. The daemon stores the configured set per package (including the system UI as "system"), keyed through a new preference, and the manager reads and writes it over AIDL. Included translations for all shipped languages.
1 parent 5e4dcb9 commit e3b15cf

32 files changed

Lines changed: 922 additions & 18 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.matrix.vector.daemon.data
2+
3+
import android.os.Process
4+
5+
/** Pure process matching rules shared by the daemon policy and local unit tests. */
6+
object InlineHookProcessPolicy {
7+
fun matchesSystemUiVirtualPackage(
8+
configuredPackages: Set<String>,
9+
processName: String,
10+
uid: Int
11+
): Boolean =
12+
SYSTEM_UI_VIRTUAL_PACKAGE in configuredPackages &&
13+
uid == Process.SYSTEM_UID &&
14+
processName == SYSTEM_UI_PROCESS
15+
16+
fun matchesPackage(
17+
expectedUid: Int,
18+
actualUid: Int,
19+
processName: String,
20+
applicationProcessName: String?,
21+
componentProcesses: Set<String>
22+
): Boolean =
23+
expectedUid == actualUid &&
24+
(processName == applicationProcessName || processName in componentProcesses)
25+
26+
fun mayInvalidate(processName: String, uid: Int): Boolean =
27+
uid != Process.SYSTEM_UID || processName != "system"
28+
}

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

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ package org.matrix.vector.daemon.data
33
import android.content.ContentValues
44
import android.database.sqlite.SQLiteDatabase
55
import org.apache.commons.lang3.SerializationUtilsX
6+
import org.matrix.vector.daemon.system.*
67

78
private const val TAG = "VectorPreferenceStore"
9+
private const val INVALIDATE_ART_INLINE_HOOKS_KEY_PREFIX = "invalidate_art_inline_hooks:"
10+
const val SYSTEM_UI_VIRTUAL_PACKAGE = "system"
11+
const val SYSTEM_UI_PROCESS = "system:ui"
812

913
object PreferenceStore {
1014

@@ -100,4 +104,56 @@ object PreferenceStore {
100104
fun isScopeRequestBlocked(pkg: String): Boolean =
101105
(getModulePrefs("lspd", 0, "config")["scope_request_blocked"] as? Set<*>)?.contains(pkg) ==
102106
true
107+
108+
fun getInvalidateArtInlineHookPackages(): Set<String> {
109+
return getModulePrefs("lspd", 0, "config")
110+
.asSequence()
111+
.filter { (key, value) ->
112+
key.startsWith(INVALIDATE_ART_INLINE_HOOKS_KEY_PREFIX) && value == true
113+
}
114+
.map { (key, _) -> key.removePrefix(INVALIDATE_ART_INLINE_HOOKS_KEY_PREFIX) }
115+
.filter { it.isNotBlank() }
116+
.toSet()
117+
}
118+
119+
/** Updates one package without replacing another Manager client's choices. */
120+
fun setInvalidateArtInlineHooks(packageName: String, enabled: Boolean): Boolean {
121+
val normalized = packageName.trim()
122+
if (normalized.isEmpty()) return false
123+
updateModulePref(
124+
"lspd",
125+
0,
126+
"config",
127+
INVALIDATE_ART_INLINE_HOOKS_KEY_PREFIX + normalized,
128+
if (enabled) true else null)
129+
return true
130+
}
131+
132+
/**
133+
* Resolves the configured package list against the actual process topology for this user.
134+
* This deliberately avoids assuming that every Android process name starts with its package name.
135+
*/
136+
fun shouldInvalidateArtInlineHooks(processName: String, uid: Int): Boolean {
137+
val configured = getInvalidateArtInlineHookPackages()
138+
if (configured.isEmpty()) return false
139+
140+
if (InlineHookProcessPolicy.matchesSystemUiVirtualPackage(configured, processName, uid)) {
141+
return true
142+
}
143+
144+
val userId = uid / PER_USER_RANGE
145+
return configured.any { packageName ->
146+
if (packageName == SYSTEM_UI_VIRTUAL_PACKAGE) return@any false
147+
val info =
148+
packageManager?.getPackageInfoWithComponents(packageName, MATCH_ALL_FLAGS, userId)
149+
?: return@any false
150+
val applicationInfo = info.applicationInfo ?: return@any false
151+
InlineHookProcessPolicy.matchesPackage(
152+
expectedUid = applicationInfo.uid,
153+
actualUid = uid,
154+
processName = processName,
155+
applicationProcessName = applicationInfo.processName,
156+
componentProcesses = info.fetchProcesses())
157+
}
158+
}
103159
}

daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/FrameworkService.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import org.matrix.vector.ipc.IProcessChannel
1515
import org.matrix.vector.ipc.IFrameworkService
1616
import org.matrix.vector.daemon.data.ConfigCache
1717
import org.matrix.vector.daemon.data.FileSystem
18+
import org.matrix.vector.daemon.data.InlineHookProcessPolicy
19+
import org.matrix.vector.daemon.data.PreferenceStore
1820
import org.matrix.vector.daemon.system.FIRST_APPLICATION_UID
1921
import org.matrix.vector.daemon.system.PER_USER_RANGE
2022
import org.matrix.vector.daemon.utils.InstallerVerifier
@@ -29,6 +31,8 @@ const val DEX_TRANSACTION_CODE =
2931
('_'.code shl 24) or ('D'.code shl 16) or ('E'.code shl 8) or 'X'.code
3032
const val OBFUSCATION_MAP_TRANSACTION_CODE =
3133
('_'.code shl 24) or ('O'.code shl 16) or ('B'.code shl 8) or 'F'.code
34+
const val INVALIDATE_ART_INLINE_HOOKS_TRANSACTION_CODE =
35+
('_'.code shl 24) or ('I'.code shl 16) or ('N'.code shl 8) or 'L'.code
3236

3337
/**
3438
* What an injected process asks the framework for — this project's `IFrameworkService`.
@@ -241,6 +245,15 @@ object FrameworkService : IFrameworkService.Stub() {
241245
}
242246
return true
243247
}
248+
INVALIDATE_ART_INLINE_HOOKS_TRANSACTION_CODE -> {
249+
val info = ensureRegistered()
250+
val invalidate =
251+
InlineHookProcessPolicy.mayInvalidate(info.processName, info.key.uid) &&
252+
PreferenceStore.shouldInvalidateArtInlineHooks(info.processName, info.key.uid)
253+
reply?.writeNoException()
254+
reply?.writeInt(if (invalidate) 1 else 0)
255+
return true
256+
}
244257
}
245258
return super.onTransact(code, data, reply, flags)
246259
}

daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/ManagerService.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,12 @@ object ManagerService : IManagerService.Stub() {
284284
if (isVerboseLogEnabled()) LogcatMonitor.startVerbose() else LogcatMonitor.stopVerbose()
285285
}
286286

287+
override fun getInvalidateArtInlineHookPackages(): MutableList<String> =
288+
PreferenceStore.getInvalidateArtInlineHookPackages().sorted().toMutableList()
289+
290+
override fun setInvalidateArtInlineHooks(packageName: String, enabled: Boolean): Boolean =
291+
PreferenceStore.setInvalidateArtInlineHooks(packageName, enabled)
292+
287293
override fun getLogParts(verbose: Boolean): List<String> = FileSystem.listLogParts(verbose)
288294

289295
override fun getLogPart(verbose: Boolean, name: String): ParcelFileDescriptor? =

manager/src/debug/kotlin/org/matrix/vector/manager/demo/FakeManagerService.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,12 @@ class FakeManagerService(
224224
real?.setVerboseLogEnabled(enabled)
225225
}
226226

227+
override fun getInvalidateArtInlineHookPackages(): MutableList<String> =
228+
real?.invalidateArtInlineHookPackages.orEmpty().sorted().toMutableList()
229+
230+
override fun setInvalidateArtInlineHooks(packageName: String, enabled: Boolean): Boolean =
231+
real?.setInvalidateArtInlineHooks(packageName, enabled) ?: false
232+
227233
override fun getLiveLogPart(verbose: Boolean): ParcelFileDescriptor? =
228234
real?.getLiveLogPart(verbose)
229235

manager/src/main/kotlin/org/matrix/vector/manager/ipc/DaemonClient.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,25 @@ class DaemonClient(private val serviceState: StateFlow<IManagerService?>) {
218218
suspend fun setVerboseLogEnabled(enabled: Boolean): Result<Unit> = runIpc { it.setVerboseLogEnabled(enabled)
219219
}
220220

221+
/**
222+
* Every package opted into ART inline-hook invalidation, sorted.
223+
*
224+
* Empty against a daemon too old to answer the call, in which case the manager shows none.
225+
*/
226+
suspend fun getInvalidateArtInlineHookPackages(): Result<List<String>> = runIpc {
227+
it.invalidateArtInlineHookPackages.orEmpty()
228+
}
229+
230+
/**
231+
* Sets whether a package invalidates Vector's native ART inline hooks after injection.
232+
*
233+
* [Result] carries the daemon's own answer: it stores the choice and reports whether the write
234+
* landed, so a blank package name or a refused write reaches the caller rather than reading as a
235+
* silent success.
236+
*/
237+
suspend fun setInvalidateArtInlineHooks(packageName: String, enabled: Boolean): Result<Boolean> =
238+
runIpc { it.setInvalidateArtInlineHooks(packageName, enabled) }
239+
221240
/**
222241
* The rotated parts the daemon still holds for one of the two logs, oldest first.
223242
*

manager/src/main/kotlin/org/matrix/vector/manager/ui/components/PackageActionMenu.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import android.text.format.Formatter
5252
import androidx.compose.material.icons.rounded.ArrowCircleUp
5353
import androidx.compose.material.icons.rounded.CloudDownload
5454
import androidx.compose.material.icons.rounded.CloudOff
55+
import androidx.compose.material.icons.rounded.FlashOff
5556
import androidx.compose.material.icons.rounded.NotificationsOff
5657
import androidx.compose.runtime.getValue
5758
import androidx.compose.runtime.mutableStateOf
@@ -363,6 +364,50 @@ LocalizedOverlay {
363364
}
364365
}
365366

367+
// The inverse of re-optimizing, also only for a hook target. Where re-optimizing clears
368+
// the inlined-away hooks ART has baked in, this stops Vector from installing ART inline
369+
// hooks in the first place — the same silence, but a compatibility escape hatch rather
370+
// than a fix: it trades the hooks of every module against an app that otherwise breaks or
371+
// crashes. Read and written per package through the daemon, so the switch starts as the
372+
// stored value and flips only as far as the daemon agrees.
373+
if (!isModule) {
374+
var invalidateInlineHooks by remember(packageName) { mutableStateOf<Boolean?>(null) }
375+
LaunchedEffect(packageName) {
376+
invalidateInlineHooks =
377+
daemon.getInvalidateArtInlineHookPackages().getOrNull()?.contains(packageName)
378+
}
379+
ActionToggleRow(
380+
icon = Icons.Rounded.FlashOff,
381+
title = stringResource(R.string.action_invalidate_art_inline_hooks),
382+
subtitle = stringResource(R.string.action_invalidate_art_inline_hooks_summary),
383+
checked = invalidateInlineHooks == true,
384+
onCheckedChange = { enabled ->
385+
finish {
386+
val ok =
387+
daemon
388+
.setInvalidateArtInlineHooks(packageName, enabled)
389+
.onFailure { e ->
390+
logE(
391+
"actions: set ART inline hook invalidation for " +
392+
"$packageName failed",
393+
e,
394+
)
395+
}
396+
.getOrDefault(false)
397+
PackageActionResult(
398+
when {
399+
!ok -> R.string.action_invalidate_art_inline_hooks_failed
400+
enabled -> R.string.action_invalidate_art_inline_hooks_enabled
401+
else -> R.string.action_invalidate_art_inline_hooks_disabled
402+
},
403+
appName,
404+
tone = if (ok) SnackbarTone.Success else SnackbarTone.Failure,
405+
)
406+
}
407+
},
408+
)
409+
}
410+
366411
if (isModule) {
367412
HorizontalDivider(Modifier.padding(horizontal = 24.dp, vertical = 4.dp))
368413
ActionDrawerItem(

manager/src/main/res/values-ar/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,4 +464,9 @@
464464
<string name="launcher_prompt_title">ليس لـ Vector أيقونة بعد</string>
465465
<string name="launcher_prompt_body">يعمل Vector داخل عملية أخرى بدل أن يكون مثبَّتًا، فلا يظهر شيء في المشغّل ولا توجد طريقة واضحة للعودة إليه. امنحه اختصارًا على الشاشة الرئيسية، أو ثبِّته كتطبيق عادي.</string>
466466
<string name="launcher_prompt_never">عدم السؤال مجددًا</string>
467+
<string name="action_invalidate_art_inline_hooks">وضع توافق ربط ART المضمّن</string>
468+
<string name="action_invalidate_art_inline_hooks_summary">تعطيل ربط ART المضمّن الخاص بـ Vector في هذا التطبيق لتحسين التوافق. قد تتوقف بعض الوحدات عن العمل هنا، وقد يتعطل التطبيق أو ينهار. يُطبّق عند تشغيل التطبيق في المرة القادمة.</string>
469+
<string name="action_invalidate_art_inline_hooks_enabled">تم تفعيل توافق ربط ART المضمّن لـ %1$s.</string>
470+
<string name="action_invalidate_art_inline_hooks_disabled">تم تعطيل توافق ربط ART المضمّن لـ %1$s.</string>
471+
<string name="action_invalidate_art_inline_hooks_failed">تعذّر تغيير توافق ربط ART المضمّن لـ %1$s.</string>
467472
</resources>

manager/src/main/res/values-de/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,4 +420,9 @@
420420
<string name="launcher_prompt_title">Vector hat noch kein Symbol</string>
421421
<string name="launcher_prompt_body">Vector läuft in einem fremden Prozess, statt installiert zu sein — im Launcher erscheint also nichts, und es gibt keinen offensichtlichen Weg zurück. Gib ihm eine Verknüpfung auf dem Startbildschirm, oder installiere es als gewöhnliche App.</string>
422422
<string name="launcher_prompt_never">Nicht mehr fragen</string>
423+
<string name="action_invalidate_art_inline_hooks">ART-Inline-Hook-Kompatibilitätsmodus</string>
424+
<string name="action_invalidate_art_inline_hooks_summary">Deaktiviere Vector\'s ART-Inline-Hooks in dieser App, um die Kompatibilität zu verbessern. Einige Module funktionieren hier möglicherweise nicht mehr, und die App kann sich fehlverhalten oder abstürzen. Wirkt beim nächsten Start der App.</string>
425+
<string name="action_invalidate_art_inline_hooks_enabled">ART-Inline-Hook-Kompatibilität für %1$s aktiviert.</string>
426+
<string name="action_invalidate_art_inline_hooks_disabled">ART-Inline-Hook-Kompatibilität für %1$s deaktiviert.</string>
427+
<string name="action_invalidate_art_inline_hooks_failed">ART-Inline-Hook-Kompatibilität für %1$s konnte nicht geändert werden.</string>
423428
</resources>

manager/src/main/res/values-es/strings.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,4 +420,9 @@
420420
<string name="launcher_prompt_title">Vector todavía no tiene icono</string>
421421
<string name="launcher_prompt_body">Vector se ejecuta dentro de otro proceso en lugar de estar instalado, así que no aparece nada en tu launcher y no hay una forma evidente de volver. Dale un acceso directo en la pantalla de inicio, o instálalo como una app normal.</string>
422422
<string name="launcher_prompt_never">No volver a preguntar</string>
423+
<string name="action_invalidate_art_inline_hooks">Modo de compatibilidad de hooks inline de ART</string>
424+
<string name="action_invalidate_art_inline_hooks_summary">Desactiva los hooks inline de ART de Vector en esta app para mejorar la compatibilidad. Algunos módulos pueden dejar de funcionar aquí, y la app puede comportarse mal o bloquearse. Se aplica la próxima vez que se inicie la app.</string>
425+
<string name="action_invalidate_art_inline_hooks_enabled">Compatibilidad de hooks inline de ART habilitada para %1$s.</string>
426+
<string name="action_invalidate_art_inline_hooks_disabled">Compatibilidad de hooks inline de ART deshabilitada para %1$s.</string>
427+
<string name="action_invalidate_art_inline_hooks_failed">No se pudo cambiar la compatibilidad de hooks inline de ART para %1$s.</string>
423428
</resources>

0 commit comments

Comments
 (0)