From fbdf5dedf66c1c94e85055e6ddb3234713bb4df6 Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Thu, 6 Aug 2026 20:59:37 +0600 Subject: [PATCH] Reduce per-process hook overhead for resource-constrained devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every scoped app's cold start pays whatever cost the hook installation itself takes, so it's the one place worth trimming here — there's no standing service or background work anywhere else in the module. Previously any enabled feature caused both Settings.Secure and Settings.Global to get hooked with the full combined key set, even though each key only ever lives on one of them (accessibility/mock-location keys are Secure-only, adb/dev-options keys are Global-only). Splitting the key maps by actual canonical class means an app that only needs e.g. ADB+dev- options hidden skips hooking Settings.Secure entirely, roughly halving the reflective hook-install calls for that case. Co-Authored-By: Claude Sonnet 5 --- .../main/java/space/bdeshi/veil/VeilHook.kt | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/space/bdeshi/veil/VeilHook.kt b/app/src/main/java/space/bdeshi/veil/VeilHook.kt index 919a669..43dc505 100644 --- a/app/src/main/java/space/bdeshi/veil/VeilHook.kt +++ b/app/src/main/java/space/bdeshi/veil/VeilHook.kt @@ -14,17 +14,34 @@ import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam private const val SELF_PACKAGE = "space.bdeshi.veil" -private val NULL_STRING_KEYS_BY_FEATURE = mapOf( +// Keyed by the Settings class each key actually lives on, so we only ever install hooks on +// the one class that could plausibly be asked about a given feature — never both. +private val SECURE_NULL_STRING_KEYS_BY_FEATURE = mapOf( Features.ACCESSIBILITY to setOf("enabled_accessibility_services"), ) -private val ZERO_VALUE_KEYS_BY_FEATURE = mapOf( +private val SECURE_ZERO_VALUE_KEYS_BY_FEATURE = mapOf( Features.ACCESSIBILITY to setOf("accessibility_enabled", "touch_exploration_enabled"), - Features.ADB to setOf("adb_enabled", "adb_wifi_enabled"), - Features.DEV_OPTIONS to setOf("development_settings_enabled"), Features.MOCK_LOCATION to setOf("mock_location", "allow_mock_location"), ) +private val GLOBAL_ZERO_VALUE_KEYS_BY_FEATURE = mapOf( + Features.ADB to setOf("adb_enabled", "adb_wifi_enabled"), + Features.DEV_OPTIONS to setOf("development_settings_enabled"), +) + +private fun keysForEnabledFeatures( + enabled: Set, + byFeature: Map>, +): Set { + if (enabled.isEmpty() || byFeature.isEmpty()) return emptySet() + val result = mutableSetOf() + for (feature in enabled) { + byFeature[feature]?.let { result.addAll(it) } + } + return result +} + class VeilHook : IXposedHookLoadPackage { override fun handleLoadPackage(lpparam: LoadPackageParam) { @@ -38,11 +55,15 @@ class VeilHook : IXposedHookLoadPackage { hookAccessibilityManager(lpparam) } - val nullStringKeys = enabled.flatMap { NULL_STRING_KEYS_BY_FEATURE[it].orEmpty() }.toSet() - val zeroValueKeys = enabled.flatMap { ZERO_VALUE_KEYS_BY_FEATURE[it].orEmpty() }.toSet() - if (nullStringKeys.isNotEmpty() || zeroValueKeys.isNotEmpty()) { - hookSettingsClass("android.provider.Settings\$Secure", lpparam, nullStringKeys, zeroValueKeys) - hookSettingsClass("android.provider.Settings\$Global", lpparam, nullStringKeys, zeroValueKeys) + val secureNullKeys = keysForEnabledFeatures(enabled, SECURE_NULL_STRING_KEYS_BY_FEATURE) + val secureZeroKeys = keysForEnabledFeatures(enabled, SECURE_ZERO_VALUE_KEYS_BY_FEATURE) + if (secureNullKeys.isNotEmpty() || secureZeroKeys.isNotEmpty()) { + hookSettingsClass("android.provider.Settings\$Secure", lpparam, secureNullKeys, secureZeroKeys) + } + + val globalZeroKeys = keysForEnabledFeatures(enabled, GLOBAL_ZERO_VALUE_KEYS_BY_FEATURE) + if (globalZeroKeys.isNotEmpty()) { + hookSettingsClass("android.provider.Settings\$Global", lpparam, emptySet(), globalZeroKeys) } if (Features.ROOT_APPS in enabled) {