Skip to content

Commit ea515ae

Browse files
committed
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
1 parent b07f6e0 commit ea515ae

File tree

9 files changed

+84
-24
lines changed

9 files changed

+84
-24
lines changed

api/current.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22920,7 +22920,7 @@ package android.view {
2292022920
method public android.view.View findFocus();
2292122921
method public final android.view.View findViewById(int);
2292222922
method public final android.view.View findViewWithTag(java.lang.Object);
22923-
method public void findViewsWithText(java.util.ArrayList<android.view.View>, java.lang.CharSequence);
22923+
method public void findViewsWithText(java.util.ArrayList<android.view.View>, java.lang.CharSequence, int);
2292422924
method protected boolean fitSystemWindows(android.graphics.Rect);
2292522925
method public boolean fitsSystemWindows();
2292622926
method public android.view.View focusSearch(int);
@@ -23249,6 +23249,8 @@ package android.view {
2324923249
field protected static final int[] ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET;
2325023250
field protected static final int[] ENABLED_STATE_SET;
2325123251
field protected static final int[] ENABLED_WINDOW_FOCUSED_STATE_SET;
23252+
field public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 2; // 0x2
23253+
field public static final int FIND_VIEWS_WITH_TEXT = 1; // 0x1
2325223254
field public static final int FOCUSABLES_ALL = 0; // 0x0
2325323255
field public static final int FOCUSABLES_TOUCH_MODE = 1; // 0x1
2325423256
field protected static final int[] FOCUSED_SELECTED_STATE_SET;

core/java/android/view/View.java

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import android.os.Parcelable;
4646
import android.os.RemoteException;
4747
import android.os.SystemClock;
48+
import android.text.TextUtils;
4849
import android.util.AttributeSet;
4950
import android.util.FloatProperty;
5051
import android.util.LocaleUtil;
@@ -1927,6 +1928,20 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
19271928
*/
19281929
public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x0000FFFF;
19291930

1931+
/**
1932+
* Find views that render the specified text.
1933+
*
1934+
* @see #findViewsWithText(ArrayList, CharSequence, int)
1935+
*/
1936+
public static final int FIND_VIEWS_WITH_TEXT = 0x00000001;
1937+
1938+
/**
1939+
* Find find views that contain the specified content description.
1940+
*
1941+
* @see #findViewsWithText(ArrayList, CharSequence, int)
1942+
*/
1943+
public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 0x00000002;
1944+
19301945
/**
19311946
* Controls the over-scroll mode for this view.
19321947
* See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)},
@@ -5132,12 +5147,28 @@ public void addFocusables(ArrayList<View> views, int direction, int focusableMod
51325147

51335148
/**
51345149
* Finds the Views that contain given text. The containment is case insensitive.
5135-
* As View's text is considered any text content that View renders.
5150+
* The search is performed by either the text that the View renders or the content
5151+
* description that describes the view for accessibility purposes and the view does
5152+
* not render or both. Clients can specify how the search is to be performed via
5153+
* passing the {@link #FIND_VIEWS_WITH_TEXT} and
5154+
* {@link #FIND_VIEWS_WITH_CONTENT_DESCRIPTION} flags.
51365155
*
51375156
* @param outViews The output list of matching Views.
5138-
* @param text The text to match against.
5157+
* @param searched The text to match against.
5158+
*
5159+
* @see #FIND_VIEWS_WITH_TEXT
5160+
* @see #FIND_VIEWS_WITH_CONTENT_DESCRIPTION
5161+
* @see #setContentDescription(CharSequence)
51395162
*/
5140-
public void findViewsWithText(ArrayList<View> outViews, CharSequence text) {
5163+
public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
5164+
if ((flags & FIND_VIEWS_WITH_CONTENT_DESCRIPTION) != 0 && !TextUtils.isEmpty(searched)
5165+
&& !TextUtils.isEmpty(mContentDescription)) {
5166+
String searchedLowerCase = searched.toString().toLowerCase();
5167+
String contentDescriptionLowerCase = mContentDescription.toString().toLowerCase();
5168+
if (contentDescriptionLowerCase.contains(searchedLowerCase)) {
5169+
outViews.add(this);
5170+
}
5171+
}
51415172
}
51425173

51435174
/**

core/java/android/view/ViewGroup.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -802,13 +802,15 @@ public void addFocusables(ArrayList<View> views, int direction, int focusableMod
802802
}
803803

804804
@Override
805-
public void findViewsWithText(ArrayList<View> outViews, CharSequence text) {
805+
public void findViewsWithText(ArrayList<View> outViews, CharSequence text, int flags) {
806+
super.findViewsWithText(outViews, text, flags);
806807
final int childrenCount = mChildrenCount;
807808
final View[] children = mChildren;
808809
for (int i = 0; i < childrenCount; i++) {
809810
View child = children[i];
810-
if ((child.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
811-
child.findViewsWithText(outViews, text);
811+
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
812+
&& (child.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
813+
child.findViewsWithText(outViews, text, flags);
812814
}
813815
}
814816
}

core/java/android/view/ViewRootImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4661,7 +4661,8 @@ public void findAccessibilityNodeInfosByViewTextUiThread(Message message) {
46614661
return;
46624662
}
46634663

4664-
root.findViewsWithText(foundViews, text);
4664+
root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
4665+
| View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
46654666
if (foundViews.isEmpty()) {
46664667
return;
46674668
}

core/java/android/view/accessibility/AccessibilityNodeInfo.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ public boolean performAction(int action) {
261261
* Finds {@link AccessibilityNodeInfo}s by text. The match is case
262262
* insensitive containment. The search is relative to this info i.e.
263263
* this info is the root of the traversed tree.
264+
*
264265
* <p>
265266
* <strong>Note:</strong> It is a client responsibility to recycle the
266267
* received info by calling {@link AccessibilityNodeInfo#recycle()}

core/java/android/widget/TextView.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8677,18 +8677,15 @@ protected int computeVerticalScrollExtent() {
86778677
}
86788678

86798679
@Override
8680-
public void findViewsWithText(ArrayList<View> outViews, CharSequence searched) {
8681-
if (TextUtils.isEmpty(searched)) {
8682-
return;
8683-
}
8684-
CharSequence thisText = getText();
8685-
if (TextUtils.isEmpty(thisText)) {
8686-
return;
8687-
}
8688-
String searchedLowerCase = searched.toString().toLowerCase();
8689-
String thisTextLowerCase = thisText.toString().toLowerCase();
8690-
if (thisTextLowerCase.contains(searchedLowerCase)) {
8691-
outViews.add(this);
8680+
public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8681+
super.findViewsWithText(outViews, searched, flags);
8682+
if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8683+
&& !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8684+
String searchedLowerCase = searched.toString().toLowerCase();
8685+
String textLowerCase = mText.toString().toLowerCase();
8686+
if (textLowerCase.contains(searchedLowerCase)) {
8687+
outViews.add(this);
8688+
}
86928689
}
86938690
}
86948691

core/tests/coretests/res/layout/interrogation_activity.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
android:layout_width="160px"
7171
android:layout_height="100px"
7272
android:text="@string/button6"
73+
android:contentDescription="contentDescription"
7374
/>
7475
</LinearLayout>
7576

core/tests/coretests/src/android/accessibilityservice/InterrogationActivityTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,29 @@ public void testFindAccessibilityNodeInfoByViewText() throws Exception {
147147
}
148148
}
149149

150+
@LargeTest
151+
public void testFindAccessibilityNodeInfoByViewTextContentDescription() throws Exception {
152+
beforeClassIfNeeded();
153+
final long startTimeMillis = SystemClock.uptimeMillis();
154+
try {
155+
// bring up the activity
156+
getActivity();
157+
158+
// find a view by text
159+
List<AccessibilityNodeInfo> buttons = AccessibilityInteractionClient.getInstance()
160+
.findAccessibilityNodeInfosByViewTextInActiveWindow(getConnection(),
161+
"contentDescription");
162+
assertEquals(1, buttons.size());
163+
} finally {
164+
afterClassIfNeeded();
165+
if (DEBUG) {
166+
final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
167+
Log.i(LOG_TAG, "testFindAccessibilityNodeInfoByViewTextContentDescription: "
168+
+ elapsedTimeMillis + "ms");
169+
}
170+
}
171+
}
172+
150173
@LargeTest
151174
public void testTraverseAllViews() throws Exception {
152175
beforeClassIfNeeded();

services/java/com/android/server/accessibility/AccessibilityManagerService.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -489,14 +489,16 @@ public IAccessibilityServiceConnection registerEventListener(IEventListener list
489489
if (oldService != null) {
490490
tryRemoveServiceLocked(oldService);
491491
}
492+
// Now this service is enabled.
493+
mEnabledServices.add(componentName);
494+
// Also make sure this service is the only one.
495+
Settings.Secure.putString(mContext.getContentResolver(),
496+
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
497+
componentName.flattenToString());
492498
// This API is intended for testing so enable accessibility to make
493499
// sure clients can start poking with the window content.
494500
Settings.Secure.putInt(mContext.getContentResolver(),
495501
Settings.Secure.ACCESSIBILITY_ENABLED, 1);
496-
// Also disable all accessibility services to avoid interference
497-
// with the tests.
498-
Settings.Secure.putString(mContext.getContentResolver(),
499-
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
500502
}
501503
AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
502504
accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;

0 commit comments

Comments
 (0)