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:
Jackal Guo
2019-10-03 10:48:26 +08:00
parent 198f9bdb30
commit fbac83822e
7 changed files with 87 additions and 26 deletions

View File

@@ -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

View File

@@ -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(

View File

@@ -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;

View File

@@ -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)) {

View File

@@ -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);

View File

@@ -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("}]");
}

View File

@@ -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