Stop HyperOS from randomly disabling accessibility services
On Android, when an accessibility app is force-stopped via ActivityManager, the accessibility permission of the app is stripped.
boolean onPackagesForceStoppedLocked(String[] packages, AccessibilityUserState userState) {
final Set<String> packageSet = Set.of(packages);
final ArrayList<ComponentName> continuousServices = userState.mInstalledServices.stream()
.filter(s -> (s.flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON) == FLAG_REQUEST_ACCESSIBILITY_BUTTON)
.map(AccessibilityServiceInfo::getComponentName)
.filter(name -> packageSet.contains(name.getPackageName()))
.collect(Collectors.toCollection(ArrayList::new));
final boolean enabledServicesChanged = userState.mEnabledServices.removeIf(comp -> {
if (packageSet.contains(comp.getPackageName())) {
userState.getBindingServicesLocked().remove(comp);
userState.getCrashedServicesLocked().remove(comp);
return true;
}
return false;
});
if (enabledServicesChanged) {
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
userState.mEnabledServices, userState.mUserId);
}
}
...And HyperOS is hardcoded to trigger a force-stop (not a kill) on
certain events like when the user is entering Ultra Battery Saver (LockScreenClean),
and others:
<ultra battery saver was enabled>
I ActivityManager: Force stopping com.urbandroid.lux appid=10415 user=0: LockScreenClean
D ActivityManager: Force removing proc 8855:com.urbandroid.lux:background/u0a415 (com.urbandroid.lux:background/10415)
This module installs a background script that monitors the logcat stream for accessibility update events
with minimal system overhead.
On boot, it checks what accessibility services are enabled, and writes them to a11y_watchlist.txt. It also listens for signs of manual
configuration changes (done by user in settings) and updates the watchlist automatically.
When an accessibility update event is detected, the background script first checks if the device is in the Settings app (as mentioned above).
If not, the background script performs a verification of enabled accessibility services against the local a11y_watchlist.txt.
And if the list doesn't match up, the changes are reverted instantly.
This background script also runs at OOM score -800 to prevent LMKD from killing it in the background. And if it still gets killed under memory pressure,
another (monitor) script (that runs at -900) is running in background to restart it (provided it didn't get killed aswell lol)
On my device, these two frameworks cause a range of problems for some reason, so i decided to follow this approach instead.
It's also more minimal this way, having no requirements at all.
- When Ultra Battery Saver is turned on, the accessibility services will still keep running in the background
- Force stopping an app via App Manager will not (permanently) remove it's accessibility permission anymore. So be careful at what you enable
- Download the module archive here
- Flash it in magisk app, or using command line
- HyperOS 1.0+ (or any A14+ system)
- KernelSU 1.0.2+
- Magisk 28.0+