From ea515aeafa01de6f50c854ee381b972ef2478284 Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Wed, 14 Sep 2011 18:15:32 -0700 Subject: [PATCH] Update the public APIs for finding views by text to optionally use content description. 1. Added flags to the search method to specify whether to match text or content description or both. 2. Added test case for the seach by content description. 3. Updated the code in AccessibilityManager service to reflect the latest changes there so test automation service works - this is the fake service used for UI automation. Change-Id: I14a6779a920ff0430e78947ea5aaf876c2e66076 --- api/current.txt | 4 +- core/java/android/view/View.java | 37 +++++++++++++++++-- core/java/android/view/ViewGroup.java | 8 ++-- core/java/android/view/ViewRootImpl.java | 3 +- .../accessibility/AccessibilityNodeInfo.java | 1 + core/java/android/widget/TextView.java | 21 +++++------ .../res/layout/interrogation_activity.xml | 1 + .../InterrogationActivityTest.java | 23 ++++++++++++ .../AccessibilityManagerService.java | 10 +++-- 9 files changed, 84 insertions(+), 24 deletions(-) diff --git a/api/current.txt b/api/current.txt index 0b6c2459bebba..ce78f9e9d5af2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -22920,7 +22920,7 @@ package android.view { method public android.view.View findFocus(); method public final android.view.View findViewById(int); method public final android.view.View findViewWithTag(java.lang.Object); - method public void findViewsWithText(java.util.ArrayList, java.lang.CharSequence); + method public void findViewsWithText(java.util.ArrayList, java.lang.CharSequence, int); method protected boolean fitSystemWindows(android.graphics.Rect); method public boolean fitsSystemWindows(); method public android.view.View focusSearch(int); @@ -23249,6 +23249,8 @@ package android.view { field protected static final int[] ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET; field protected static final int[] ENABLED_STATE_SET; field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET; + field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2 + field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1 field public static final int FOCUSABLES_ALL = 0; // 0x0 field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1 field protected static final int[] FOCUSED_SELECTED_STATE_SET; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 65e98578ac4bd..ca06b9cf118a0 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -45,6 +45,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.FloatProperty; import android.util.LocaleUtil; @@ -1927,6 +1928,20 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x0000FFFF; + /** + * Find views that render the specified text. + * + * @see #findViewsWithText(ArrayList, CharSequence, int) + */ + public static final int FIND_VIEWS_WITH_TEXT = 0x00000001; + + /** + * Find find views that contain the specified content description. + * + * @see #findViewsWithText(ArrayList, CharSequence, int) + */ + public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 0x00000002; + /** * Controls the over-scroll mode for this view. * See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)}, @@ -5132,12 +5147,28 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Finds the Views that contain given text. The containment is case insensitive. - * As View's text is considered any text content that View renders. + * The search is performed by either the text that the View renders or the content + * description that describes the view for accessibility purposes and the view does + * not render or both. Clients can specify how the search is to be performed via + * passing the {@link #FIND_VIEWS_WITH_TEXT} and + * {@link #FIND_VIEWS_WITH_CONTENT_DESCRIPTION} flags. * * @param outViews The output list of matching Views. - * @param text The text to match against. + * @param searched The text to match against. + * + * @see #FIND_VIEWS_WITH_TEXT + * @see #FIND_VIEWS_WITH_CONTENT_DESCRIPTION + * @see #setContentDescription(CharSequence) */ - public void findViewsWithText(ArrayList outViews, CharSequence text) { + public void findViewsWithText(ArrayList outViews, CharSequence searched, int flags) { + if ((flags & FIND_VIEWS_WITH_CONTENT_DESCRIPTION) != 0 && !TextUtils.isEmpty(searched) + && !TextUtils.isEmpty(mContentDescription)) { + String searchedLowerCase = searched.toString().toLowerCase(); + String contentDescriptionLowerCase = mContentDescription.toString().toLowerCase(); + if (contentDescriptionLowerCase.contains(searchedLowerCase)) { + outViews.add(this); + } + } } /** diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 1bd07820664ad..c7b59b8b79f09 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -802,13 +802,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override - public void findViewsWithText(ArrayList outViews, CharSequence text) { + public void findViewsWithText(ArrayList outViews, CharSequence text, int flags) { + super.findViewsWithText(outViews, text, flags); final int childrenCount = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < childrenCount; i++) { View child = children[i]; - if ((child.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) { - child.findViewsWithText(outViews, text); + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE + && (child.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) { + child.findViewsWithText(outViews, text, flags); } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 81f9d78d1e011..46119843951fe 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4661,7 +4661,8 @@ public final class ViewRootImpl extends Handler implements ViewParent, return; } - root.findViewsWithText(foundViews, text); + root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT + | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION); if (foundViews.isEmpty()) { return; } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index f0e8005e0c05a..7671312a5704d 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -261,6 +261,7 @@ public class AccessibilityNodeInfo implements Parcelable { * Finds {@link AccessibilityNodeInfo}s by text. The match is case * insensitive containment. The search is relative to this info i.e. * this info is the root of the traversed tree. + * *

* Note: It is a client responsibility to recycle the * received info by calling {@link AccessibilityNodeInfo#recycle()} diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index edb1bfcf4e1e3..d78a7a36e67cd 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8677,18 +8677,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } @Override - public void findViewsWithText(ArrayList outViews, CharSequence searched) { - if (TextUtils.isEmpty(searched)) { - return; - } - CharSequence thisText = getText(); - if (TextUtils.isEmpty(thisText)) { - return; - } - String searchedLowerCase = searched.toString().toLowerCase(); - String thisTextLowerCase = thisText.toString().toLowerCase(); - if (thisTextLowerCase.contains(searchedLowerCase)) { - outViews.add(this); + public void findViewsWithText(ArrayList outViews, CharSequence searched, int flags) { + super.findViewsWithText(outViews, searched, flags); + if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 + && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { + String searchedLowerCase = searched.toString().toLowerCase(); + String textLowerCase = mText.toString().toLowerCase(); + if (textLowerCase.contains(searchedLowerCase)) { + outViews.add(this); + } } } diff --git a/core/tests/coretests/res/layout/interrogation_activity.xml b/core/tests/coretests/res/layout/interrogation_activity.xml index 44ed75c75fb5a..64af321d3619e 100644 --- a/core/tests/coretests/res/layout/interrogation_activity.xml +++ b/core/tests/coretests/res/layout/interrogation_activity.xml @@ -70,6 +70,7 @@ android:layout_width="160px" android:layout_height="100px" android:text="@string/button6" + android:contentDescription="contentDescription" /> diff --git a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java index a542a1bcc5d0c..cd8dcb9732299 100644 --- a/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java +++ b/core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java @@ -147,6 +147,29 @@ public class InterrogationActivityTest } } + @LargeTest + public void testFindAccessibilityNodeInfoByViewTextContentDescription() throws Exception { + beforeClassIfNeeded(); + final long startTimeMillis = SystemClock.uptimeMillis(); + try { + // bring up the activity + getActivity(); + + // find a view by text + List buttons = AccessibilityInteractionClient.getInstance() + .findAccessibilityNodeInfosByViewTextInActiveWindow(getConnection(), + "contentDescription"); + assertEquals(1, buttons.size()); + } finally { + afterClassIfNeeded(); + if (DEBUG) { + final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; + Log.i(LOG_TAG, "testFindAccessibilityNodeInfoByViewTextContentDescription: " + + elapsedTimeMillis + "ms"); + } + } + } + @LargeTest public void testTraverseAllViews() throws Exception { beforeClassIfNeeded(); diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 10d384b47502e..6830055367e4a 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -489,14 +489,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (oldService != null) { tryRemoveServiceLocked(oldService); } + // Now this service is enabled. + mEnabledServices.add(componentName); + // Also make sure this service is the only one. + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + componentName.flattenToString()); // This API is intended for testing so enable accessibility to make // sure clients can start poking with the window content. Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, 1); - // Also disable all accessibility services to avoid interference - // with the tests. - Settings.Secure.putString(mContext.getContentResolver(), - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, ""); } AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo(); accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;