The new icon is a pearl crescent on deep indigo, with the rest of the
moon still faintly visible behind it: hidden, not removed.
- ic_launcher_background: radial indigo gradient, replacing the flat
colour resource (values/colors.xml, now unused, is removed)
- ic_launcher_foreground: faint full disc plus a gradient crescent,
scaled to sit inside the 66 dp safe zone
- ic_launcher_monochrome: new; solid crescent over the disc at 30%
alpha, used for Android 13+ themed icons
Both adaptive icons (regular and round) reference all three layers.
All shapes are plain paths and gradients, no masks. Built with
assembleDebug and checked in the APK with aapt2; not yet seen on a
device.
Co-Authored-By: agent <agent@bdeshi.space>
With per-app configuration gone, the hidden list is the same for every app
and can't be edited at runtime, so it carries the well-known names instead
of only KernelSU, Magisk and Veil. 28 packages plus Veil, grouped in
Features.kt:
- root managers: Magisk, Magisk Delta, Magisk Alpha, KernelSU, KernelSU
Next, SukiSU Ultra, APatch
- module managers and helpers: MMRL (both variants), Magisk Module
Manager, KsuWebUI
- hook frameworks and managers: Vector, LSPosed, LSPatch, EdXposed,
Xposed installer, Taichi, VirtualXposed, Substrate
- older su apps and one-click root tools
Packages that aren't installed match nothing, so a name that's wrong or
absent is harmless. Most names were not checked against an install; only
KernelSU and MMRL were present on the test device. Xposed modules in
general have no common package name, so they stay uncovered by name.
The settings screen shows the count and puts the list behind a toggle.
Verified on device with Veil Probe: the installed ones (KernelSU and both
MMRL packages) report HIDDEN across all ten lookups, no LEAK/FAIL/ERROR,
and a scoped app still starts.
Co-Authored-By: agent <agent@bdeshi.space>
An app could skip Settings.Secure/Global and read the settings provider
straight through ContentResolver, getting the real accessibility, ADB and
developer-options state. Two routes, both now masked for the same keys the
Settings hooks cover:
- ContentResolver.call(content://settings/<table>, "GET_<table>", key):
the returned Bundle's "value" is rewritten to null / "0".
- ContentResolver.query on settings/secure and settings/global: the
cursor is replaced with a MatrixCursor with the value masked. The key is
read from the "name" column, or, when a caller projects only "value",
from the URI path or a single selection arg. Cursors that touch no
masked key are left as they were.
Verified on device with Veil Probe: call(), query() with name=?, and the
settings/global/<key> path form all report HIDDEN for accessibility,
adb_enabled and development_settings_enabled, and a scoped app still starts.
Co-Authored-By: agent <agent@bdeshi.space>
Veil Probe showed KernelSU and Veil still resolvable through lookups the
hooks didn't cover, so an app could find either by skipping getPackageInfo:
- getPackageUid(name, flags) -> NameNotFoundException
- getPackageInfo(VersionedPackage, flags) -> NameNotFoundException
- getApplicationEnabledSetting(name) -> IllegalArgumentException,
which is what the real call throws for an unknown package
- getLaunchIntentForPackage(name) -> null
Same pattern as the existing hooks: set the result or throwable before the
original runs, so errors for packages that really don't exist still come
from the platform unwrapped.
Verified on device with Veil Probe: all eight rows (four lookups x KernelSU
and Veil) now report HIDDEN, no LEAK/FAIL/ERROR, and a scoped app still starts.
Co-Authored-By: agent <agent@bdeshi.space>
Per-app settings never worked: a hooked app reads Veil's config through
XSharedPreferences, which for a legacy module points at Veil's private
data dir (/data/data/space.bdeshi.veil/shared_prefs). Hooked apps can't
read it, so every app silently fell back to "hide everything" and every
toggle, hidden-package edit and picker entry was ignored. Confirmed on
device with Veil Probe: unticking a feature changed nothing.
The framework's fix for this (xposedsharedprefs, redirecting the module's
prefs dir to one hooked apps can read) needs the framework to hook Veil's
own process. On Vector v2.2 here it never does, even with Veil in its own
scope and after a reboot, so there's no reliable channel from Veil's app
to a hooked app.
So stop pretending there is one:
- VeilHook applies all six features with the fixed Features.HIDDEN_PACKAGES
(KernelSU, Magisk, Veil) to every app in scope, with no prefs read.
- Remove discovery: the Application#attach hook, the exported provider
(one less way for an app to spot Veil) and the launcher <queries>.
- The settings screen becomes a read-only summary plus the help panel;
help tips cover leaving an app out of scope, changing the list by
rebuilding, and turning verbose logging off afterwards, since it
names Veil in each hooked app's own logcat.
Verified on device: all 24 testable hooked paths in Veil Probe stay
hidden, Veil's provider no longer resolves, a scoped app still loads Veil.
Co-Authored-By: agent <agent@bdeshi.space>
Auto-discovery only works for apps that can see Veil: on Android 11+ an
app without QUERY_ALL_PACKAGES (or a matching <queries>) can't resolve
Veil's provider, so its first-launch check-in silently fails. A real app hit this: scoped and hooked, but never listed. That made 615f000's premise,
that auto-detection covers every target, wrong.
- "Add app" opens a searchable list of launchable apps; picking one saves
its default feature set, which is what gives an app a card. A
launcher-intent <queries> is enough for this and narrower than
QUERY_ALL_PACKAGES; it widens what Veil sees, not who sees Veil.
- App cards show the app's label above the package name.
- reportScoped checks resolveContentProvider first and skips the call
when Veil isn't visible. That also drops the framework's
"Failed to find provider info for space.bdeshi.veil.provider" line,
which named Veil in the target's own, app-readable logcat.
- New help tip explains why a scoped app may not show up.
Verified on device (Android 16, Vector): a scoped app no longer logs the
provider failure, and adding it through the picker saves its feature set.
Co-Authored-By: agent <agent@bdeshi.space>
An installed hooking module is as much a tell as a root manager, but
Veil's own package wasn't in the hidden list, so any scoped app could
find it with getPackageInfo or the installed-package lists. It's now a
default alongside KernelSU and Magisk.
DEFAULT_HIDDEN_PACKAGES only applies while no list has been saved, so a
one-time migration adds Veil to an existing list the first time the
settings screen opens; a flag stops it from re-adding Veil after a
manual removal.
Discovery is unaffected: the hooked process resolves Veil's provider
through ActivityManager, not the hooked ApplicationPackageManager.
Verified on device (Android 16, Vector) with Veil Probe: getPackageInfo
for Veil now throws NameNotFoundException, the probe still registers,
and every other hooked path stays hidden.
Co-Authored-By: agent <agent@bdeshi.space>
The settings screen doesn't need its own launcher icon — LSPosed Manager
already provides the entry point for opening a module's settings, via
PackageManager.getLaunchIntentForPackage(), which resolves CATEGORY_INFO
before falling back to CATEGORY_LAUNCHER. Switching to INFO keeps it
launchable that way while dropping it from the home-screen app drawer.
Also: targeting SDK 35 enforces edge-to-edge by default, and the screen
never handled system-bar insets, so content drew straight under the status
bar. fitsSystemWindows on the content root fixes the overlap.
Co-Authored-By: agent <agent@bdeshi.space>
A static versionName only matches a release tag if someone remembers to bump
it first — nothing enforces that, so they drift. versionName now comes from
`git describe --tags` (the tag itself when built exactly on a tagged commit,
otherwise the usual git-describe dev form), and versionCode from commit
count. Cutting a release is now just tagging a commit; there's nothing left
to hand-edit.
Co-Authored-By: agent <agent@bdeshi.space>
The target app crashed on launch after the root/manager-app and Settings
hooks landed. The actual bug: the pass-through branch of both
hookSettingsClass and hookPackageManager used XC_MethodReplacement and
manually called XposedBridge.invokeOriginalMethod() when a call wasn't one
we wanted to intercept. That routes through Method.invoke(), which wraps
*any* exception the real method throws in InvocationTargetException — including
completely normal ones like NameNotFoundException for a not-installed
package, or SettingNotFoundException for an unrecognized key, unrelated to
anything we hide. The target app's own root-detection code checks a list of
known manager packages and expects to catch NameNotFoundException directly;
the wrapped exception type broke that on the very first miss.
Fixed by switching both to XC_MethodHook.beforeHookedMethod, setting
param.result/param.throwable only for the keys/packages we actually want to
affect and leaving param untouched otherwise. That lets the real method run
through its normal, non-reflective path for everything else, so its
exceptions propagate correctly.
Confirmed on-device: the target app launches and stays in the foreground
with all six features enabled.
Co-Authored-By: agent <agent@bdeshi.space>
- Real release build type: R8 shrinking + shrunk resources, with a keep
rule for VeilHook specifically — LSPosed finds it by exact class name via
the plaintext xposed_init asset, which R8 can't see, so obfuscating it
would silently break module loading with no build error to point at why.
- Signing config that reads keystore.properties if present (gitignored,
generated via scripts/generate-keystore.sh), falling back to debug
signing otherwise so assembleRelease always produces something
installable.
- scripts/release.sh builds and copies a version-stamped APK into dist/.
- GitHub Actions workflow building and publishing a release on any tag
matching *.*.* (bare semver, e.g. 1.2.0 — no v prefix required), optionally
using repo secrets for real release signing in CI.
- README covering setup, the feature table, and the DenyList gotcha.
No native code in this module, so a single APK already covers every
architecture — no per-ABI splitting needed.
Co-Authored-By: agent <agent@bdeshi.space>
If an app is genuinely scoped in LSPosed, Veil already picks it up on its
own. If it isn't scoped, typing its package name here wouldn't do anything
anyway — the hook never runs in that process. The manual entry field was
redundant either way.
Co-Authored-By: agent <agent@bdeshi.space>
The screen was built entirely from raw android.widget.* views with no
theme applied at all, so it never had a chance to follow system dark/light
mode and looked dated regardless. Adds AppCompat + Material Components,
switches SettingsActivity to AppCompatActivity with a Theme.Material3.
DayNight theme, and rebuilds the screen with Material widgets (cards,
chips for the hidden-packages list, proper text fields).
Also adds a launcher icon (previously none): a vector adaptive icon, a
moon-like disc partially eclipsed by a diagonal veil in the background
color — no image tool involved, just XML.
Also renames the per-app "Remove" button to "Apply defaults (hide
everything)" — "Remove" read as removing the app from the list, not
resetting it to defaults, which is all it actually does.
Co-Authored-By: agent <agent@bdeshi.space>
Manually re-typing a package name Veil already got injected into was
redundant work. The hook now reports "I got loaded into package X" to a
small ContentProvider in Veil's own app the moment a scoped app's process
starts (found via hooking Application#attach for a reliable early Context),
which records it to SharedPreferences. Veil's settings screen lists the
union of explicitly configured apps and auto-detected ones, tagging the
latter so it's clear they haven't been customized yet.
No persistent service: the OS starts Veil's process on demand for each
call() and can kill it again right after, same footprint as everything
else in this module.
Co-Authored-By: agent <agent@bdeshi.space>
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: agent <agent@bdeshi.space>
Collapsible panel on the settings screen covering what came up debugging
this against a real target app: the KernelSU "Umount modules" / DenyList
conflict that silently blocks Zygisk injection, checking a separate Zygisk
implementation's own DenyList, needing a force-stop + relaunch (not a full
reboot) after config changes, and using LSPosed's verbose logging to confirm
injection actually happened.
Co-Authored-By: agent <agent@bdeshi.space>
Checkbox labels like "Accessibility services" read as status, not action —
unclear whether checking it meant "hide this" or something else. Replaces
the flat label map with a label + one-line description per feature, and
adds a short caption on each app card spelling out what checking a box
actually does.
Co-Authored-By: agent <agent@bdeshi.space>
Renamed since the module hides more than accessibility state now — hook class
and app display name become VeilHook/Veil (the module already lived under the
space.bdeshi.veil namespace).
Two new features, same per-app toggle model as everything else:
- Root/manager apps: hides packages in a configurable shared list (default
KernelSU + Magisk) from PackageManager's getPackageInfo/getApplicationInfo/
getInstalledPackages/getInstalledApplications.
- Build tags/type: overwrites Build.TAGS/Build.TYPE to release-keys/user for
the hooked process, hiding the test-keys/userdebug markers apps sometimes
check for.
Co-Authored-By: agent <agent@bdeshi.space>
Replaces "hide everything for every scoped app" with per-package, per-feature
toggles, backed by SharedPreferences the hook reads via XSharedPreferences.
A package with no explicit entry still gets every feature hidden, keeping
prior behavior as the default.
Adds a launcher activity so the settings screen is directly reachable.
Co-Authored-By: agent <agent@bdeshi.space>
LSPosed module that hooks AccessibilityManager and Settings.Secure/Global
(getString/getStringForUser/getInt/getIntForUser) inside whatever app
process LSPosed injects it into, so that process sees no accessibility
services and no adb/wifi-debugging flags enabled. Target apps are chosen
via the module's scope list in LSPosed Manager, not in-app config.
Confirmed working against a real target app on-device; it previously
refused to launch with an accessibility service active and now starts
normally with the module enabled and scoped to it. Required disabling
KernelSU's "umount modules" option for that app, since it otherwise blocks
Zygisk (and therefore LSPosed) from injecting into the process at all.
Co-Authored-By: agent <agent@bdeshi.space>