Allow restarting of crashed a11y services
After an accessibility service crashes a few times in a short period
of time, framework would add it into a “blacklist”. User wouldn’t be
able to use (re-enable) it. If users express an intent in them working
again, we remove the crashed accessibility service from the blacklist.
Bug: 129689483
Test: a11y CTS & unit tests
Test: manual
1. Install and enable an intentional crashed a11y service.
2. After it's crashed and malfunctioning, re-enable it.
3. Check if it's bound again.
Change-Id: Ia89121f11bc5e05fc829e0e3ecb266150409678b
This commit is contained in:
@@ -428,7 +428,7 @@ public class AccessibilityServiceInfo implements Parcelable {
|
||||
|
||||
/**
|
||||
* Whether or not the service has crashed and is awaiting restart. Only valid from {@link
|
||||
* android.view.accessibility.AccessibilityManager#getEnabledAccessibilityServiceList(int)},
|
||||
* android.view.accessibility.AccessibilityManager#getInstalledAccessibilityServiceList()},
|
||||
* because that is populated from the internal list of running services.
|
||||
*
|
||||
* @hide
|
||||
|
||||
@@ -707,10 +707,10 @@ public final class AccessibilityManager {
|
||||
try {
|
||||
services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId);
|
||||
if (DEBUG) {
|
||||
Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
|
||||
Log.i(LOG_TAG, "Enabled AccessibilityServices " + services);
|
||||
}
|
||||
} catch (RemoteException re) {
|
||||
Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
|
||||
Log.e(LOG_TAG, "Error while obtaining the enabled AccessibilityServices. ", re);
|
||||
}
|
||||
if (mAccessibilityPolicy != null) {
|
||||
services = mAccessibilityPolicy.getEnabledAccessibilityServiceList(
|
||||
|
||||
@@ -48,11 +48,21 @@ public class AccessibilityUtils {
|
||||
return getEnabledServicesFromSettings(context, UserHandle.myUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the accessibility service is crashed
|
||||
*
|
||||
* @param packageName The package name to check
|
||||
* @param serviceName The service name to check
|
||||
* @param installedServiceInfos The list of installed accessibility service
|
||||
* @return {@code true} if the accessibility service is crashed for the user.
|
||||
* {@code false} otherwise.
|
||||
*/
|
||||
public static boolean hasServiceCrashed(String packageName, String serviceName,
|
||||
List<AccessibilityServiceInfo> enabledServiceInfos) {
|
||||
for (int i = 0; i < enabledServiceInfos.size(); i++) {
|
||||
AccessibilityServiceInfo accessibilityServiceInfo = enabledServiceInfos.get(i);
|
||||
final ServiceInfo serviceInfo = enabledServiceInfos.get(i).getResolveInfo().serviceInfo;
|
||||
List<AccessibilityServiceInfo> installedServiceInfos) {
|
||||
for (int i = 0; i < installedServiceInfos.size(); i++) {
|
||||
final AccessibilityServiceInfo accessibilityServiceInfo = installedServiceInfos.get(i);
|
||||
final ServiceInfo serviceInfo =
|
||||
installedServiceInfos.get(i).getResolveInfo().serviceInfo;
|
||||
if (TextUtils.equals(serviceInfo.packageName, packageName)
|
||||
&& TextUtils.equals(serviceInfo.name, serviceName)) {
|
||||
return accessibilityServiceInfo.crashed;
|
||||
|
||||
@@ -366,9 +366,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
if (userId != mCurrentUserId) {
|
||||
return;
|
||||
}
|
||||
AccessibilityUserState userState = getUserStateLocked(userId);
|
||||
boolean reboundAService = userState.getBindingServicesLocked().removeIf(
|
||||
final AccessibilityUserState userState = getUserStateLocked(userId);
|
||||
final boolean reboundAService = userState.getBindingServicesLocked().removeIf(
|
||||
component -> component != null
|
||||
&& component.getPackageName().equals(packageName))
|
||||
|| userState.mCrashedServices.removeIf(component -> component != null
|
||||
&& component.getPackageName().equals(packageName));
|
||||
if (reboundAService) {
|
||||
onUserStateChangedLocked(userState);
|
||||
@@ -393,6 +395,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
if (compPkg.equals(packageName)) {
|
||||
it.remove();
|
||||
userState.getBindingServicesLocked().remove(comp);
|
||||
userState.getCrashedServicesLocked().remove(comp);
|
||||
// Update the enabled services setting.
|
||||
persistComponentNamesToSettingLocked(
|
||||
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
|
||||
@@ -753,6 +756,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
userState.mEnabledServices.clear();
|
||||
userState.mEnabledServices.add(service);
|
||||
userState.getBindingServicesLocked().clear();
|
||||
userState.getCrashedServicesLocked().clear();
|
||||
userState.mTouchExplorationGrantedServices.clear();
|
||||
userState.mTouchExplorationGrantedServices.add(service);
|
||||
|
||||
@@ -1178,6 +1182,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
AccessibilityServiceInfo accessibilityServiceInfo;
|
||||
try {
|
||||
accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext);
|
||||
if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) {
|
||||
// Restore the crashed attribute.
|
||||
accessibilityServiceInfo.crashed = true;
|
||||
}
|
||||
mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo);
|
||||
} catch (XmlPullParserException | IOException xppe) {
|
||||
Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe);
|
||||
@@ -1418,8 +1426,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
continue;
|
||||
}
|
||||
|
||||
// Wait for the binding if it is in process.
|
||||
if (userState.getBindingServicesLocked().contains(componentName)) {
|
||||
// Skip the component since it may be in process or crashed.
|
||||
if (userState.getBindingServicesLocked().contains(componentName)
|
||||
|| userState.getCrashedServicesLocked().contains(componentName)) {
|
||||
continue;
|
||||
}
|
||||
if (userState.mEnabledServices.contains(componentName)
|
||||
@@ -2687,6 +2696,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
}
|
||||
} else if (mEnabledAccessibilityServicesUri.equals(uri)) {
|
||||
if (readEnabledAccessibilityServicesLocked(userState)) {
|
||||
userState.updateCrashedServicesIfNeededLocked();
|
||||
onUserStateChangedLocked(userState);
|
||||
}
|
||||
} else if (mTouchExplorationGrantedAccessibilityServicesUri.equals(uri)) {
|
||||
|
||||
@@ -62,9 +62,6 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
|
||||
|
||||
private final Handler mMainHandler;
|
||||
|
||||
private boolean mWasConnectedAndDied;
|
||||
|
||||
|
||||
AccessibilityServiceConnection(AccessibilityUserState userState, Context context,
|
||||
ComponentName componentName,
|
||||
AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
|
||||
@@ -168,8 +165,6 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
|
||||
|
||||
@Override
|
||||
public AccessibilityServiceInfo getServiceInfo() {
|
||||
// Update crashed data
|
||||
mAccessibilityServiceInfo.crashed = mWasConnectedAndDied;
|
||||
return mAccessibilityServiceInfo;
|
||||
}
|
||||
|
||||
@@ -178,10 +173,13 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
|
||||
synchronized (mLock) {
|
||||
AccessibilityUserState userState = mUserStateWeakReference.get();
|
||||
if (userState == null) return;
|
||||
Set<ComponentName> bindingServices = userState.getBindingServicesLocked();
|
||||
if (bindingServices.contains(mComponentName) || mWasConnectedAndDied) {
|
||||
final Set<ComponentName> bindingServices = userState.getBindingServicesLocked();
|
||||
final Set<ComponentName> crashedServices = userState.getCrashedServicesLocked();
|
||||
if (bindingServices.contains(mComponentName)
|
||||
|| crashedServices.contains(mComponentName)) {
|
||||
bindingServices.remove(mComponentName);
|
||||
mWasConnectedAndDied = false;
|
||||
crashedServices.remove(mComponentName);
|
||||
mAccessibilityServiceInfo.crashed = false;
|
||||
serviceInterface = mServiceInterface;
|
||||
}
|
||||
// There's a chance that service is removed from enabled_accessibility_services setting
|
||||
@@ -271,7 +269,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
|
||||
if (!isConnectedLocked()) {
|
||||
return;
|
||||
}
|
||||
mWasConnectedAndDied = true;
|
||||
mAccessibilityServiceInfo.crashed = true;
|
||||
AccessibilityUserState userState = mUserStateWeakReference.get();
|
||||
if (userState != null) {
|
||||
userState.serviceDisconnectedLocked(this);
|
||||
|
||||
@@ -73,6 +73,8 @@ class AccessibilityUserState {
|
||||
|
||||
final Set<ComponentName> mBindingServices = new HashSet<>();
|
||||
|
||||
final Set<ComponentName> mCrashedServices = new HashSet<>();
|
||||
|
||||
final Set<ComponentName> mEnabledServices = new HashSet<>();
|
||||
|
||||
final Set<ComponentName> mTouchExplorationGrantedServices = new HashSet<>();
|
||||
@@ -127,6 +129,7 @@ class AccessibilityUserState {
|
||||
// Clear service management state.
|
||||
mBoundServices.clear();
|
||||
mBindingServices.clear();
|
||||
mCrashedServices.clear();
|
||||
|
||||
// Clear event management state.
|
||||
mLastSentClientState = -1;
|
||||
@@ -184,15 +187,16 @@ class AccessibilityUserState {
|
||||
|
||||
/**
|
||||
* Make sure a services disconnected but still 'on' state is reflected in AccessibilityUserState
|
||||
* There are three states to a service here: off, bound, and binding.
|
||||
* This drops a service from a bound state, to the binding state.
|
||||
* The binding state describes the situation where a service is on, but not bound.
|
||||
* There are four states to a service here: off, bound, and binding, and crashed.
|
||||
* This drops a service from a bound state, to the crashed state.
|
||||
* The crashed state describes the situation where a service used to be bound, but no longer is
|
||||
* despite still being enabled.
|
||||
*
|
||||
* @param serviceConnection The service.
|
||||
*/
|
||||
void serviceDisconnectedLocked(AccessibilityServiceConnection serviceConnection) {
|
||||
removeServiceLocked(serviceConnection);
|
||||
mBindingServices.add(serviceConnection.getComponentName());
|
||||
mCrashedServices.add(serviceConnection.getComponentName());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,10 +293,20 @@ class AccessibilityUserState {
|
||||
mBindInstantServiceAllowed = allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns binding service list.
|
||||
*/
|
||||
Set<ComponentName> getBindingServicesLocked() {
|
||||
return mBindingServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns crashed service list.
|
||||
*/
|
||||
Set<ComponentName> getCrashedServicesLocked() {
|
||||
return mCrashedServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns enabled service list.
|
||||
*/
|
||||
@@ -300,6 +314,23 @@ class AccessibilityUserState {
|
||||
return mEnabledServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove service from crashed service list if users disable it.
|
||||
*/
|
||||
void updateCrashedServicesIfNeededLocked() {
|
||||
for (int i = 0, count = mInstalledServices.size(); i < count; i++) {
|
||||
final AccessibilityServiceInfo installedService = mInstalledServices.get(i);
|
||||
final ComponentName componentName = ComponentName.unflattenFromString(
|
||||
installedService.getId());
|
||||
|
||||
if (mCrashedServices.contains(componentName)
|
||||
&& !mEnabledServices.contains(componentName)) {
|
||||
// Remove it from mCrashedServices since users toggle the switch bar to retry.
|
||||
mCrashedServices.remove(componentName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<AccessibilityServiceConnection> getBoundServicesLocked() {
|
||||
return mBoundServices;
|
||||
}
|
||||
@@ -439,6 +470,18 @@ class AccessibilityUserState {
|
||||
pw.append(componentName.toShortString());
|
||||
}
|
||||
}
|
||||
pw.println("}");
|
||||
pw.append(" Crashed services:{");
|
||||
it = mCrashedServices.iterator();
|
||||
if (it.hasNext()) {
|
||||
ComponentName componentName = it.next();
|
||||
pw.append(componentName.toShortString());
|
||||
while (it.hasNext()) {
|
||||
componentName = it.next();
|
||||
pw.append(", ");
|
||||
pw.append(componentName.toShortString());
|
||||
}
|
||||
}
|
||||
pw.println("}]");
|
||||
}
|
||||
|
||||
|
||||
@@ -207,14 +207,14 @@ public class AccessibilityUserStateTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serviceDisconnected_removeServiceAndAddToBinding() {
|
||||
public void serviceDisconnected_removeServiceAndAddToCrashed() {
|
||||
when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
|
||||
mUserState.addServiceLocked(mMockConnection);
|
||||
|
||||
mUserState.serviceDisconnectedLocked(mMockConnection);
|
||||
|
||||
assertFalse(mUserState.getBoundServicesLocked().contains(mMockConnection));
|
||||
assertTrue(mUserState.getBindingServicesLocked().contains(COMPONENT_NAME));
|
||||
assertTrue(mUserState.getCrashedServicesLocked().contains(COMPONENT_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user