From db7da0eb8b7d515c168d5b410764e24c9a0f9431 Mon Sep 17 00:00:00 2001 From: Svetoslav Date: Mon, 22 Apr 2013 18:34:02 -0700 Subject: Fixing bugs exposed when moving accessibility CTS tests to UiAutomation. 1. UiAutomation#executeAndWaitForEvent method was invoking the passed runnable while holding the lock which may lead to a deadlock. For example: a runnable that calls getActivity() gets us into a state like this. 2. UI automation services did not get all capabilities such a service can have. Now a UI test service gets all of them. 3. When UiAutomation was exiting for event fired as a result of a performed action, it was checking whether the received evnet time is strictly before the time of executing the command that should fire the event. However, if the execution is fast enough, i.e. less than one millisecond, then the event time and the execution time are the same. This was leading to a missed signal in rare cases. 4. AccessibilityNodeInfoCache was not clearing the relevant state for accessibility focus clearing event. 5. Accessibility text traversal in TextView was partially using text and partially content description - broken. Now we are using the text since for text view and content desc for other views. In other words, we are using the most precise text we have. 6. AccessibilityManagerService was not granting capabilities of a UiAutomation service - plainly wrong. CTS change:https://googleplex-android-review.googlesource.com/#/c/300693/ bug:8695422 bug:8657560 Change-Id: I9afc5c3c69eb51f1c01930959232f44681b15e86 --- .../AccessibilityServiceInfo.java | 17 +++++++++++++ core/java/android/app/UiAutomation.java | 19 ++++++++++----- core/java/android/app/UiAutomationConnection.java | 2 +- .../accessibility/AccessibilityNodeInfoCache.java | 1 + core/java/android/widget/TextView.java | 28 ++++++---------------- .../accessibility/AccessibilityManagerService.java | 17 +++++++------ 6 files changed, 47 insertions(+), 37 deletions(-) diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 40f45b780485..7e21db3893f6 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -375,6 +375,23 @@ public class AccessibilityServiceInfo implements Parcelable { /* do nothing */ } + /** + * Creates a new instance. + * + * @param isAutomation Whether this is a test automation service. + * + * @hide + */ + public AccessibilityServiceInfo(boolean isAutomation) { + // Automation service can do anything. + if (isAutomation) { + mCapabilities |= CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT + | CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION + | CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY + | CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS; + } + } + /** * Creates a new instance. * diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 05b79c1d7a8e..498fa4264fe5 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -443,18 +443,25 @@ public final class UiAutomation { */ public AccessibilityEvent executeAndWaitForEvent(Runnable command, AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException { + // Acquire the lock and prepare for receiving events. synchronized (mLock) { throwIfNotConnectedLocked(); - mEventQueue.clear(); // Prepare to wait for an event. mWaitingForEventDelivery = true; + } - // We will ignore events from previous interactions. - final long executionStartTimeMillis = SystemClock.uptimeMillis(); + // Note: We have to release the lock since calling out with this lock held + // can bite. We will correctly filter out events from other interactions, + // so starting to collect events before running the action is just fine. - // Execute the command. - command.run(); + // We will ignore events from previous interactions. + final long executionStartTimeMillis = SystemClock.uptimeMillis(); + // Execute the command *without* the lock being held. + command.run(); + + // Acquire the lock and wait for the event. + synchronized (mLock) { try { // Wait for the event. final long startTimeMillis = SystemClock.uptimeMillis(); @@ -463,7 +470,7 @@ public final class UiAutomation { while (!mEventQueue.isEmpty()) { AccessibilityEvent event = mEventQueue.remove(0); // Ignore events from previous interactions. - if (event.getEventTime() <= executionStartTimeMillis) { + if (event.getEventTime() < executionStartTimeMillis) { continue; } if (filter.accept(event)) { diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 97c7ff3ea86b..5bc17fae1f9e 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -158,7 +158,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client) { IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); - AccessibilityServiceInfo info = new AccessibilityServiceInfo(); + AccessibilityServiceInfo info = new AccessibilityServiceInfo(true); info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK; info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC; info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java index 14954bea735a..28518aab8ab9 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java @@ -83,6 +83,7 @@ public class AccessibilityNodeInfoCache { } break; case AccessibilityEvent.TYPE_VIEW_FOCUSED: case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: + case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: case AccessibilityEvent.TYPE_VIEW_SELECTED: case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1246051a09bf..9e3f87f447bc 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8042,7 +8042,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener info.setEditable(true); } - if (TextUtils.isEmpty(getContentDescription()) && !TextUtils.isEmpty(mText)) { + if (!TextUtils.isEmpty(mText)) { info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER @@ -8051,6 +8051,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); } + if (isFocused()) { if (canSelectText()) { info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); @@ -8655,13 +8656,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @Override public CharSequence getIterableTextForAccessibility() { - if (!TextUtils.isEmpty(mText)) { - if (!(mText instanceof Spannable)) { - setText(mText, BufferType.SPANNABLE); - } - return mText; + if (!(mText instanceof Spannable)) { + setText(mText, BufferType.SPANNABLE); } - return super.getIterableTextForAccessibility(); + return mText; } /** @@ -8697,13 +8695,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @Override public int getAccessibilitySelectionStart() { - if (TextUtils.isEmpty(getContentDescription())) { - final int selectionStart = getSelectionStart(); - if (selectionStart >= 0) { - return selectionStart; - } - } - return ACCESSIBILITY_CURSOR_POSITION_UNDEFINED; + return getSelectionStart(); } /** @@ -8718,13 +8710,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @Override public int getAccessibilitySelectionEnd() { - if (TextUtils.isEmpty(getContentDescription())) { - final int selectionEnd = getSelectionEnd(); - if (selectionEnd >= 0) { - return selectionEnd; - } - } - return ACCESSIBILITY_CURSOR_POSITION_UNDEFINED; + return getSelectionEnd(); } /** diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 2f64908e0506..64dfd67457bc 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -542,7 +542,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return -1; } - public void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient serviceClient, + public void registerUiTestAutomationService(IBinder owner, + IAccessibilityServiceClient serviceClient, AccessibilityServiceInfo accessibilityServiceInfo) { mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT, FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE); @@ -1732,14 +1733,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS; } - if (mResolveInfo != null) { - mRequestTouchExplorationMode = (info.flags - & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0; - mRequestEnhancedWebAccessibility = (info.flags - & AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY) != 0; - mRequestFilterKeyEvents = (info.flags - & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0; - } + mRequestTouchExplorationMode = (info.flags + & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0; + mRequestEnhancedWebAccessibility = (info.flags + & AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY) != 0; + mRequestFilterKeyEvents = (info.flags + & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0; } /** -- cgit v1.2.3-59-g8ed1b