I came up with an idea to use a static boolean indicating the status of Accessibility Service and compare it with Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
. I've tested on multiple devices, haven't found any issue with this method.
1.Declare a static boolean in Accessibility Service.
private static boolean bServiceRunning = false;
2.In Accessibility Service, set the boolean value in onServiceConnected
and onUnbind
@Override
protected void onServiceConnected() {
super.onServiceConnected();
bServiceRunning = true; //put this at the very beginning to reduce time gap
}
@Override
public boolean onUnbind(Intent intent) {
bServiceRunning = false;
return super.onUnbind(intent);
}
3.Create a static function in Accessibility Service
public static boolean bGetServiceStatus(){
return bServiceRunning;
}
With the boolean flag, I can know if the accessibility service is running in the desired state. When the service is being forced to stop, onUnbind
will be called so the boolean value turns into false.
4.We use Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
to get accessibility service switch status
public static boolean bIsAccessibilityServiceEnabled(Context context, Class<?> accessibilityService) {
ComponentName expectedComponentName = new ComponentName(context, accessibilityService);
String strServicesSettingResult = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (strServicesSettingResult == null){
return false;
}
TextUtils.SimpleStringSplitter colonSplitter = new TextUtils.SimpleStringSplitter(':');
colonSplitter.setString(strServicesSettingResult);
while (colonSplitter.hasNext()) {
String strComponentName = colonSplitter.next();
ComponentName enabledService = ComponentName.unflattenFromString(strComponentName);
if (enabledService != null && enabledService.equals(expectedComponentName))
return true;
}
return false;
}
5.And this is what we want, we check with the above two methods the determine the real state of accessibility service.
public static int intIsAccessibilityServiceEnabled_WithCrashCheck(Context context, Class<?> accessibilityService){
//return 0 if Accessibility Service enabled and running
//return -1 if Accessibility Service disabled and not running
//return -2 if Accessibility Service enabled but stopped working or crashed
//first check Accessibility Service boolean
if(bGetServiceStatus()){
//service is running
return 0;
}else{
//service not running, now double check with Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
boolean bResult = bIsAccessibilityServiceEnabled(context, accessibilityService);
if(!bResult){
//Accessibility Service is disabled
return -1;
}else{
//Accessibility Service is enabled, but service is not actually running, Accessibility Service is crashed
return -2;
}
}
}
Using "AccessibilityManager" also works the same, but I prefer a more "lightweight" version with static boolean for better performance.
Note: Using Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
without another doublecheck will cause a bug. The value is not synced. The result doesn't always represent the real service status. The following steps can create such a case :
- Start an accessibility service then go to App settings force stop the App.
- Now you can see the accessibility service switch is turned off. (Seems enough currently, but the next step will create a problem)
- Start the accessibility service again,on Android 9.0+ devices you'll find out the switch is switched on, but the service is actually not running, and now it displays "Not
working. Tap for info."
2022/11/29 Edit: This bug is fixed according to this issue tracker. I can no longer reproduce this bug on my new Android 12 & 13 devices. However, devices with old Android firmware still has this bug. (The patch is also applied to the newest AVD images. To test this bug with AVD, now you must download old revisions of the AVD images.)