• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view.accessibility.cts;
18 
19 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_IN_DIRECTION;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertSame;
25 import static org.junit.Assert.assertThrows;
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assert.fail;
28 
29 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
30 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
31 import android.app.Instrumentation;
32 import android.app.UiAutomation;
33 import android.content.Context;
34 import android.os.Message;
35 import android.os.Parcel;
36 import android.os.SystemClock;
37 import android.platform.test.annotations.RequiresFlagsEnabled;
38 import android.platform.test.flag.junit.CheckFlagsRule;
39 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
40 import android.text.SpannableString;
41 import android.text.TextUtils;
42 import android.text.style.LocaleSpan;
43 import android.view.Display;
44 import android.view.View;
45 import android.view.accessibility.AccessibilityEvent;
46 import android.view.accessibility.AccessibilityNodeInfo;
47 import android.view.accessibility.AccessibilityRecord;
48 import android.view.accessibility.Flags;
49 import android.widget.LinearLayout;
50 import android.widget.TextView;
51 
52 import androidx.lifecycle.Lifecycle;
53 import androidx.test.ext.junit.rules.ActivityScenarioRule;
54 import androidx.test.ext.junit.runners.AndroidJUnit4;
55 import androidx.test.filters.SmallTest;
56 import androidx.test.platform.app.InstrumentationRegistry;
57 
58 import com.android.compatibility.common.util.ApiTest;
59 
60 import org.junit.Before;
61 import org.junit.Rule;
62 import org.junit.Test;
63 import org.junit.rules.RuleChain;
64 import org.junit.runner.RunWith;
65 
66 import java.util.ArrayList;
67 import java.util.List;
68 import java.util.Locale;
69 import java.util.concurrent.TimeoutException;
70 
71 /** Class for testing {@link AccessibilityEvent}. */
72 // TODO(b/263942937) Re-enable @Presubmit
73 @RunWith(AndroidJUnit4.class)
74 public class AccessibilityEventTest {
75     private static final long IDLE_TIMEOUT_MS = 500;
76     private static final long DEFAULT_TIMEOUT_MS = 2000;
77 
78     // From ViewConfiguration.SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS
79     private static final long SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS = 100;
80 
81     private EventReportingLinearLayout mParentView;
82     private View mChildView;
83     private TextView mTextView;
84     private String mPackageName;
85 
86     private static Instrumentation sInstrumentation;
87     private static UiAutomation sUiAutomation;
88     private final ActivityScenarioRule<DummyActivity> mActivityRule =
89             new ActivityScenarioRule<>(DummyActivity.class);
90     private final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
91             new AccessibilityDumpOnFailureRule();
92     private InstrumentedAccessibilityServiceTestRule<SpeakingAccessibilityService>
93             mInstrumentedAccessibilityServiceRule =
94                     new InstrumentedAccessibilityServiceTestRule<>(
95                             SpeakingAccessibilityService.class, false);
96 
97     private final CheckFlagsRule mCheckFlagsRule =
98             DeviceFlagsValueProvider.createCheckFlagsRule();
99 
100     @Rule
101     public final RuleChain mRuleChain =
102             RuleChain.outerRule(mActivityRule)
103                     .around(mInstrumentedAccessibilityServiceRule)
104                     .around(mDumpOnFailureRule)
105                     .around(mCheckFlagsRule);
106 
107     @Before
setUp()108     public void setUp() throws Throwable {
109         sInstrumentation = InstrumentationRegistry.getInstrumentation();
110         sUiAutomation = sInstrumentation.getUiAutomation();
111         mActivityRule
112                 .getScenario()
113                 .moveToState(Lifecycle.State.RESUMED)
114                 .onActivity(
115                         activity ->
116                                 mPackageName = activity.getApplicationContext().getPackageName());
117         mInstrumentedAccessibilityServiceRule.enableService();
118         sUiAutomation.executeAndWaitForEvent(
119                 () -> {
120                     try {
121                         mActivityRule
122                                 .getScenario()
123                                 .onActivity(
124                                         activity -> {
125                                             final LinearLayout grandparent =
126                                                     new LinearLayout(activity);
127                                             activity.setContentView(grandparent);
128                                             mParentView = new EventReportingLinearLayout(activity);
129                                             mChildView = new View(activity);
130                                             mTextView = new TextView(activity);
131                                             grandparent.addView(mParentView);
132                                             mParentView.addView(mChildView);
133                                             mParentView.addView(mTextView);
134                                         });
135                     } catch (Throwable e) {
136                         fail(e.toString());
137                     }
138                 },
139                 // There can be a race where the test Activity gets focus and we start test.
140                 // Because we don't specify flagRetrieveInteractiveWindows in this test, until
141                 // the Activity gets focus, no events will be delivered from it.
142                 // So. this waits for any event from the test activity.
143                 accessibilityEvent -> mPackageName.equals(accessibilityEvent.getPackageName()),
144                 DEFAULT_TIMEOUT_MS);
145         sUiAutomation.waitForIdle(IDLE_TIMEOUT_MS, DEFAULT_TIMEOUT_MS);
146     }
147 
148     private static class EventReportingLinearLayout extends LinearLayout {
149         public List<AccessibilityEvent> mReceivedEvents = new ArrayList<AccessibilityEvent>();
150 
EventReportingLinearLayout(Context context)151         public EventReportingLinearLayout(Context context) {
152             super(context);
153         }
154 
155         @Override
requestSendAccessibilityEvent(View child, AccessibilityEvent event)156         public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
157             mReceivedEvents.add(AccessibilityEvent.obtain(event));
158             return super.requestSendAccessibilityEvent(child, event);
159         }
160     }
161 
162     @Test
testScrollEvent()163     public void testScrollEvent() throws Exception {
164         sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
165                 () -> mChildView.scrollTo(0, 100)), new ScrollEventFilter(1), DEFAULT_TIMEOUT_MS);
166     }
167 
168     @Test
testScrollEventBurstCombined()169     public void testScrollEventBurstCombined() throws Exception {
170         sUiAutomation.executeAndWaitForEvent(
171                 () -> sInstrumentation.runOnMainSync(
172                         () -> {
173                             mChildView.scrollTo(0, 100);
174                             mChildView.scrollTo(0, 125);
175                             mChildView.scrollTo(0, 150);
176                             mChildView.scrollTo(0, 175);
177                         }),
178                 new ScrollEventFilter(1),
179                 DEFAULT_TIMEOUT_MS);
180     }
181 
182     @Test
testScrollEventsDeliveredInCorrectInterval()183     public void testScrollEventsDeliveredInCorrectInterval() throws Exception {
184         sUiAutomation.executeAndWaitForEvent(
185                 () -> {
186                     sInstrumentation.runOnMainSync(() -> {
187                         mChildView.scrollTo(0, 25);
188                         mChildView.scrollTo(0, 50);
189                         mChildView.scrollTo(0, 100);
190                     });
191                     SystemClock.sleep(SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS * 2);
192                     sInstrumentation.runOnMainSync(() -> {
193                         mChildView.scrollTo(0, 150);
194                         mChildView.scrollTo(0, 175);
195                     });
196                     SystemClock.sleep(SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS / 2);
197                     sInstrumentation.runOnMainSync(() -> {
198                         mChildView.scrollTo(0, 200);
199                     });
200                 },
201                 new ScrollEventFilter(2),
202                 DEFAULT_TIMEOUT_MS);
203     }
204 
205     class ScrollEventFilter extends AccessibilityEventFilter {
206         private int mCount = 0;
207         private int mTargetCount;
208 
ScrollEventFilter(int count)209         ScrollEventFilter(int count) {
210             mTargetCount = count;
211         }
212 
accept(AccessibilityEvent event)213         public boolean accept(AccessibilityEvent event) {
214             if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
215                 mCount += 1;
216                 mEvents.add(event);
217                 return mCount >= mTargetCount;
218             }
219             return false;
220         }
221     }
222 
223     @Test
testScrollEventsClearedOnDetach()224     public void testScrollEventsClearedOnDetach() throws Throwable {
225         ScrollEventFilter scrollEventFilter = new ScrollEventFilter(1);
226         sUiAutomation.executeAndWaitForEvent(
227                 () -> sInstrumentation.runOnMainSync(
228                         () -> {
229                             mChildView.scrollTo(0, 25);
230                             mChildView.scrollTo(5, 50);
231                             mChildView.scrollTo(7, 100);
232                         }),
233                 scrollEventFilter,
234                 DEFAULT_TIMEOUT_MS);
235         mActivityRule
236                 .getScenario()
237                 .onActivity(
238                         activity -> {
239                             mParentView.removeView(mChildView);
240                             mParentView.addView(mChildView);
241                         });
242         sUiAutomation.executeAndWaitForEvent(
243                 () -> sInstrumentation.runOnMainSync(
244                         () -> {
245                             mChildView.scrollTo(0, 150);
246                         }),
247                 scrollEventFilter,
248                 DEFAULT_TIMEOUT_MS);
249         AccessibilityEvent event = scrollEventFilter.getLastEvent();
250         assertEquals(-7, event.getScrollDeltaX());
251         assertEquals(50, event.getScrollDeltaY());
252     }
253 
254     @Test
testScrollEventsCaptureTotalDelta()255     public void testScrollEventsCaptureTotalDelta() throws Throwable {
256         ScrollEventFilter scrollEventFilter = new ScrollEventFilter(1);
257         sUiAutomation.executeAndWaitForEvent(
258                 () -> sInstrumentation.runOnMainSync(
259                         () -> {
260                             mChildView.scrollTo(0, 25);
261                             mChildView.scrollTo(5, 50);
262                             mChildView.scrollTo(7, 100);
263                         }),
264                 scrollEventFilter,
265                 DEFAULT_TIMEOUT_MS);
266         AccessibilityEvent event = scrollEventFilter.getLastEvent();
267         assertEquals(7, event.getScrollDeltaX());
268         assertEquals(100, event.getScrollDeltaY());
269     }
270 
271     @Test
testScrollEventsClearDeltaAfterSending()272     public void testScrollEventsClearDeltaAfterSending() throws Throwable {
273         ScrollEventFilter scrollEventFilter = new ScrollEventFilter(2);
274         sUiAutomation.executeAndWaitForEvent(
275                 () -> {
276                     sInstrumentation.runOnMainSync(() -> {
277                         mChildView.scrollTo(0, 25);
278                         mChildView.scrollTo(5, 50);
279                         mChildView.scrollTo(7, 100);
280                     });
281                     SystemClock.sleep(SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS * 2);
282                     sInstrumentation.runOnMainSync(() -> {
283                         mChildView.scrollTo(0, 25);
284                         mChildView.scrollTo(5, 50);
285                         mChildView.scrollTo(7, 100);
286                         mChildView.scrollTo(0, 150);
287                     });
288                 },
289                 scrollEventFilter,
290                 DEFAULT_TIMEOUT_MS);
291         AccessibilityEvent event = scrollEventFilter.getLastEvent();
292         assertEquals(-7, event.getScrollDeltaX());
293         assertEquals(50, event.getScrollDeltaY());
294     }
295 
296     @Test
testEventViewTargetedByScroll()297     public void testEventViewTargetedByScroll() throws Throwable {
298         final AccessibilityEvent awaitedEvent = sUiAutomation.executeAndWaitForEvent(
299                 () -> {
300                     AccessibilityEvent event = new AccessibilityEvent(
301                             AccessibilityEvent.TYPE_VIEW_TARGETED_BY_SCROLL);
302                     event.setAction(ACTION_SCROLL_IN_DIRECTION.getId());
303                     mChildView.sendAccessibilityEventUnchecked(event);
304                 },
305                 event -> event.getEventType() == AccessibilityEvent.TYPE_VIEW_TARGETED_BY_SCROLL,
306                 DEFAULT_TIMEOUT_MS);
307         assertThat(awaitedEvent.getAction()).isEqualTo(ACTION_SCROLL_IN_DIRECTION.getId());
308         assertThat(awaitedEvent.getSource()).isEqualTo(mChildView.createAccessibilityNodeInfo());
309     }
310 
311     @Test
312     @ApiTest(apis = {
313             "android.view.accessibility.AccessibilityEvent#CONTENT_CHANGE_TYPE_CHECKED"
314     })
315     @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_TRI_STATE_CHECKED)
testContentChangeTypeCheckedEvent()316     public void testContentChangeTypeCheckedEvent() throws Throwable {
317         final AccessibilityEvent awaitedEvent = sUiAutomation.executeAndWaitForEvent(
318                 () -> {
319                     AccessibilityEvent event = new AccessibilityEvent(
320                             AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
321                     event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_CHECKED);
322                     mChildView.sendAccessibilityEventUnchecked(event);
323                 },
324                 event -> event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
325                 DEFAULT_TIMEOUT_MS);
326         assertThat(awaitedEvent.getContentChangeTypes()).isEqualTo(
327                 AccessibilityEvent.CONTENT_CHANGE_TYPE_CHECKED);
328     }
329 
330     @Test
331     @ApiTest(apis = {"android.view.accessibility.AccessibilityEvent#CONTENT_CHANGE_TYPE_EXPANDED"})
332     @RequiresFlagsEnabled(Flags.FLAG_A11Y_EXPANSION_STATE_API)
testContentChangeTypeExpandedEvent()333     public void testContentChangeTypeExpandedEvent() throws Throwable {
334         final AccessibilityEvent awaitedEvent =
335                 sUiAutomation.executeAndWaitForEvent(
336                         () -> {
337                             AccessibilityEvent event =
338                                     new AccessibilityEvent(
339                                             AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
340                             event.setContentChangeTypes(
341                                     AccessibilityEvent.CONTENT_CHANGE_TYPE_EXPANDED);
342                             mChildView.sendAccessibilityEventUnchecked(event);
343                         },
344                         event ->
345                                 event.getEventType()
346                                         == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
347                         DEFAULT_TIMEOUT_MS);
348         assertThat(awaitedEvent.getContentChangeTypes())
349                 .isEqualTo(AccessibilityEvent.CONTENT_CHANGE_TYPE_EXPANDED);
350     }
351 
352     @Test
testStateEvent()353     public void testStateEvent() throws Throwable {
354         sUiAutomation.executeAndWaitForEvent(
355                 () -> {
356                     sendStateDescriptionChangedEvent(mChildView);
357                 },
358                 new StateDescriptionEventFilter(1),
359                 DEFAULT_TIMEOUT_MS);
360     }
361 
362     @Test
testStateEventBurstCombined()363     public void testStateEventBurstCombined() throws Throwable {
364         sUiAutomation.executeAndWaitForEvent(
365                 () -> {
366                     sendStateDescriptionChangedEvent(mChildView);
367                     sendStateDescriptionChangedEvent(mChildView);
368                     sendStateDescriptionChangedEvent(mChildView);
369                     sendStateDescriptionChangedEvent(mChildView);
370                 },
371                 new StateDescriptionEventFilter(1),
372                 DEFAULT_TIMEOUT_MS);
373     }
374 
375     @Test
testStateEventsDeliveredInCorrectInterval()376     public void testStateEventsDeliveredInCorrectInterval() throws Throwable {
377         sUiAutomation.executeAndWaitForEvent(
378                 () -> {
379                     sendStateDescriptionChangedEvent(mChildView);
380                     sendStateDescriptionChangedEvent(mChildView);
381                     sendStateDescriptionChangedEvent(mChildView);
382                     SystemClock.sleep(SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS * 2);
383                     sendStateDescriptionChangedEvent(mChildView);
384                     sendStateDescriptionChangedEvent(mChildView);
385                     SystemClock.sleep(SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS / 2);
386                     sendStateDescriptionChangedEvent(mChildView);
387                 },
388                 new StateDescriptionEventFilter(2),
389                 DEFAULT_TIMEOUT_MS);
390     }
391 
392     @Test
testStateEventsHaveLastEventText()393     public void testStateEventsHaveLastEventText() throws Throwable {
394         StateDescriptionEventFilter stateDescriptionEventFilter =
395                 new StateDescriptionEventFilter(1);
396         String expectedState = "Second state";
397         sUiAutomation.executeAndWaitForEvent(
398                 () -> {
399                     sendStateDescriptionChangedEvent(mChildView, "First state");
400                     sendStateDescriptionChangedEvent(mChildView, expectedState);
401                 },
402                 stateDescriptionEventFilter,
403                 DEFAULT_TIMEOUT_MS);
404         AccessibilityEvent event = stateDescriptionEventFilter.getLastEvent();
405         assertEquals(expectedState, event.getText().get(0));
406     }
407 
408     class StateDescriptionEventFilter extends AccessibilityEventFilter {
409         private int mCount;
410         private int mTargetCount;
411 
StateDescriptionEventFilter(int count)412         StateDescriptionEventFilter(int count) {
413             mTargetCount = count;
414         }
415 
accept(AccessibilityEvent event)416         public boolean accept(AccessibilityEvent event) {
417             if (event.getContentChangeTypes()
418                     == AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION) {
419                 mCount += 1;
420                 mEvents.add(event);
421                 return mCount >= mTargetCount;
422             }
423             return false;
424         }
425     }
426     ;
427 
428     private abstract class AccessibilityEventFilter
429             implements UiAutomation.AccessibilityEventFilter {
430         protected List<AccessibilityEvent> mEvents = new ArrayList<>();
431 
accept(AccessibilityEvent event)432         public abstract boolean accept(AccessibilityEvent event);
433 
assertReceivedEventCount(int count)434         void assertReceivedEventCount(int count) {
435             assertEquals(count, mEvents.size());
436         }
437 
getLastEvent()438         AccessibilityEvent getLastEvent() {
439             if (mEvents.size() > 0) {
440                 return mEvents.get(mEvents.size() - 1);
441             }
442             return null;
443         }
444     }
445 
sendStateDescriptionChangedEvent(View view)446     private void sendStateDescriptionChangedEvent(View view) {
447         sendStateDescriptionChangedEvent(view, null);
448     }
449 
sendStateDescriptionChangedEvent(View view, CharSequence text)450     private void sendStateDescriptionChangedEvent(View view, CharSequence text) {
451         AccessibilityEvent event =
452                 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
453         event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION);
454         event.getText().add(text);
455         view.sendAccessibilityEventUnchecked(event);
456     }
457 
458     @Test
setTextError_receiveEvent()459     public void setTextError_receiveEvent() throws Throwable {
460         sUiAutomation.executeAndWaitForEvent(
461                 () -> sInstrumentation.runOnMainSync(() -> mTextView.setError("error")),
462                 event -> isExpectedChangeType(event,
463                         AccessibilityEvent.CONTENT_CHANGE_TYPE_ERROR
464                                 | AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_INVALID)
465                         && event.getSource().getError() != null,
466                 DEFAULT_TIMEOUT_MS);
467     }
468 
469     @Test
setViewEnable_receiveEvent()470     public void setViewEnable_receiveEvent() throws Throwable {
471         sUiAutomation.executeAndWaitForEvent(
472                 () -> sInstrumentation.runOnMainSync(() -> {
473                     mChildView.setEnabled(!mChildView.isEnabled());
474                 }),
475                 event -> isExpectedChangeType(event,
476                         AccessibilityEvent.CONTENT_CHANGE_TYPE_ENABLED),
477                 DEFAULT_TIMEOUT_MS);
478     }
479 
480     @Test
setText_unChanged_doNotReceiveEvent()481     public void setText_unChanged_doNotReceiveEvent() throws Throwable {
482         sUiAutomation.executeAndWaitForEvent(
483                 () -> sInstrumentation.runOnMainSync(() -> mTextView.setText("a")),
484                 event -> isExpectedChangeType(event, AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT),
485                 DEFAULT_TIMEOUT_MS);
486 
487         assertThrows(
488                 TimeoutException.class,
489                 () ->
490                         sUiAutomation.executeAndWaitForEvent(
491                                 () -> {
492                                     sInstrumentation.runOnMainSync(
493                                             () -> {
494                                                 mTextView.setText("a");
495                                             });
496                                 },
497                                 event -> isExpectedSource(event, mTextView),
498                                 DEFAULT_TIMEOUT_MS));
499     }
500 
501     @Test
setText_textChanged_receivesTextEvent()502     public void setText_textChanged_receivesTextEvent() throws Throwable {
503         sUiAutomation.executeAndWaitForEvent(
504                 () -> sInstrumentation.runOnMainSync(() -> mTextView.setText("a")),
505                 event -> isExpectedChangeType(event, AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT),
506                 DEFAULT_TIMEOUT_MS);
507 
508         sUiAutomation.executeAndWaitForEvent(
509                 () -> {
510                     sInstrumentation.runOnMainSync(
511                             () -> {
512                                 mTextView.setText("b");
513                             });
514                 },
515                 event ->
516                         isExpectedSource(event, mTextView)
517                                 && isExpectedChangeType(
518                                         event, AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT),
519                 DEFAULT_TIMEOUT_MS);
520     }
521 
522     @Test
setText_parcelableSpanChanged_receivesUndefinedEvent()523     public void setText_parcelableSpanChanged_receivesUndefinedEvent() throws Throwable {
524         String text = "a";
525         sUiAutomation.executeAndWaitForEvent(
526                 () -> sInstrumentation.runOnMainSync(() -> mTextView.setText(text)),
527                 event -> isExpectedChangeType(event, AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT),
528                 DEFAULT_TIMEOUT_MS);
529 
530         sUiAutomation.executeAndWaitForEvent(
531                 () -> {
532                     sInstrumentation.runOnMainSync(
533                             () -> {
534                                 SpannableString spannableString = new SpannableString(text);
535                                 spannableString.setSpan(new LocaleSpan(Locale.ENGLISH), 0, 1, 0);
536                                 mTextView.setText(spannableString);
537                             });
538                 },
539                 event ->
540                         isExpectedSource(event, mTextView)
541                                 && isExpectedChangeType(
542                                         event, AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED),
543                 DEFAULT_TIMEOUT_MS);
544     }
545 
isExpectedSource(AccessibilityEvent event, View view)546     private static boolean isExpectedSource(AccessibilityEvent event, View view) {
547         return TextUtils.equals(view.getContext().getPackageName(), event.getPackageName())
548                 && TextUtils.equals(view.getAccessibilityClassName(), event.getClassName());
549     }
550 
isExpectedChangeType(AccessibilityEvent event, int changeType)551     private static boolean isExpectedChangeType(AccessibilityEvent event, int changeType) {
552         return (event.getContentChangeTypes() & changeType) == changeType;
553     }
554 
555     /**
556      * Tests whether accessibility events are correctly written and read from a parcel (version 1).
557      */
558     @SmallTest
559     @Test
testMarshaling()560     public void testMarshaling() throws Exception {
561         // fully populate the event to marshal
562         AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
563         fullyPopulateAccessibilityEvent(sentEvent);
564 
565         // marshal and unmarshal the event
566         Parcel parcel = Parcel.obtain();
567         sentEvent.writeToParcel(parcel, 0);
568         parcel.setDataPosition(0);
569         AccessibilityEvent receivedEvent = AccessibilityEvent.CREATOR.createFromParcel(parcel);
570 
571         // make sure all fields properly marshaled
572         assertEqualsAccessibilityEvent(sentEvent, receivedEvent);
573 
574         parcel.recycle();
575     }
576 
577     /** Tests if {@link AccessibilityEvent} can be acquired through obtain(). */
578     @SmallTest
579     @Test
testRecycle()580     public void testRecycle() {
581         // evaluate that recycle() can be called on an event acquired by obtain()
582         AccessibilityEvent.obtain().recycle();
583     }
584 
585     /** Tests whether the event types are correctly converted to strings. */
586     @SmallTest
587     @Test
testEventTypeToString()588     public void testEventTypeToString() {
589         assertEquals(
590                 "TYPE_NOTIFICATION_STATE_CHANGED",
591                 AccessibilityEvent.eventTypeToString(
592                         AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED));
593         assertEquals(
594                 "TYPE_TOUCH_EXPLORATION_GESTURE_END",
595                 AccessibilityEvent.eventTypeToString(
596                         AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END));
597         assertEquals(
598                 "TYPE_TOUCH_EXPLORATION_GESTURE_START",
599                 AccessibilityEvent.eventTypeToString(
600                         AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START));
601         assertEquals(
602                 "TYPE_VIEW_CLICKED",
603                 AccessibilityEvent.eventTypeToString(AccessibilityEvent.TYPE_VIEW_CLICKED));
604         assertEquals(
605                 "TYPE_VIEW_FOCUSED",
606                 AccessibilityEvent.eventTypeToString(AccessibilityEvent.TYPE_VIEW_FOCUSED));
607         assertEquals(
608                 "TYPE_VIEW_HOVER_ENTER",
609                 AccessibilityEvent.eventTypeToString(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER));
610         assertEquals(
611                 "TYPE_VIEW_HOVER_EXIT",
612                 AccessibilityEvent.eventTypeToString(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT));
613         assertEquals(
614                 "TYPE_VIEW_LONG_CLICKED",
615                 AccessibilityEvent.eventTypeToString(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED));
616         assertEquals(
617                 "TYPE_VIEW_CONTEXT_CLICKED",
618                 AccessibilityEvent.eventTypeToString(AccessibilityEvent.TYPE_VIEW_CONTEXT_CLICKED));
619         assertEquals(
620                 "TYPE_VIEW_SCROLLED",
621                 AccessibilityEvent.eventTypeToString(AccessibilityEvent.TYPE_VIEW_SCROLLED));
622         assertEquals(
623                 "TYPE_VIEW_SELECTED",
624                 AccessibilityEvent.eventTypeToString(AccessibilityEvent.TYPE_VIEW_SELECTED));
625         assertEquals(
626                 "TYPE_VIEW_TEXT_CHANGED",
627                 AccessibilityEvent.eventTypeToString(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED));
628         assertEquals(
629                 "TYPE_VIEW_TEXT_SELECTION_CHANGED",
630                 AccessibilityEvent.eventTypeToString(
631                         AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED));
632         assertEquals(
633                 "TYPE_WINDOW_CONTENT_CHANGED",
634                 AccessibilityEvent.eventTypeToString(
635                         AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED));
636         assertEquals(
637                 "TYPE_WINDOW_STATE_CHANGED",
638                 AccessibilityEvent.eventTypeToString(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED));
639     }
640 
641     /** Tests whether the event describes its contents consistently. */
642     @SmallTest
643     @Test
testDescribeContents()644     public void testDescribeContents() {
645         AccessibilityEvent event = AccessibilityEvent.obtain();
646         assertSame(
647                 "Accessibility events always return 0 for this method.",
648                 0,
649                 event.describeContents());
650         fullyPopulateAccessibilityEvent(event);
651         assertSame(
652                 "Accessibility events always return 0 for this method.",
653                 0,
654                 event.describeContents());
655     }
656 
657     /**
658      * Tests whether accessibility events are correctly written and read from a parcel (version 2).
659      */
660     @SmallTest
661     @Test
testMarshaling2()662     public void testMarshaling2() {
663         // fully populate the event to marshal
664         AccessibilityEvent marshaledEvent = AccessibilityEvent.obtain();
665         fullyPopulateAccessibilityEvent(marshaledEvent);
666 
667         // marshal and unmarshal the event
668         Parcel parcel = Parcel.obtain();
669         marshaledEvent.writeToParcel(parcel, 0);
670         parcel.setDataPosition(0);
671         AccessibilityEvent unmarshaledEvent = AccessibilityEvent.obtain();
672         unmarshaledEvent.initFromParcel(parcel);
673 
674         // make sure all fields properly marshaled
675         assertEqualsAccessibilityEvent(marshaledEvent, unmarshaledEvent);
676 
677         parcel.recycle();
678     }
679 
680     /**
681      * While CharSequence is immutable, some classes implementing it are mutable. Make sure they
682      * can't change the object by changing the objects backing CharSequence
683      */
684     @SmallTest
685     @Test
testChangeTextAfterSetting_shouldNotAffectEvent()686     public void testChangeTextAfterSetting_shouldNotAffectEvent() {
687         final String originalText = "Cassowary";
688         final String newText = "Hornbill";
689         AccessibilityEvent event = AccessibilityEvent.obtain();
690         StringBuffer updatingString = new StringBuffer(originalText);
691         event.setBeforeText(updatingString);
692         event.setContentDescription(updatingString);
693 
694         updatingString.delete(0, updatingString.length());
695         updatingString.append(newText);
696 
697         assertTrue(TextUtils.equals(originalText, event.getBeforeText()));
698         assertTrue(TextUtils.equals(originalText, event.getContentDescription()));
699     }
700 
701     @SmallTest
702     @Test
testConstructors()703     public void testConstructors() {
704         final AccessibilityEvent populatedEvent = new AccessibilityEvent();
705         fullyPopulateAccessibilityEvent(populatedEvent);
706         final AccessibilityEvent event = new AccessibilityEvent(populatedEvent);
707 
708         assertEqualsAccessibilityEvent(event, populatedEvent);
709 
710         final AccessibilityEvent firstEvent = new AccessibilityEvent();
711         firstEvent.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
712         final AccessibilityEvent secondEvent =
713                 new AccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
714 
715         assertEqualsAccessibilityEvent(firstEvent, secondEvent);
716     }
717 
718     /**
719      * Fully populates the {@link AccessibilityEvent} to marshal.
720      *
721      * @param sentEvent The event to populate.
722      */
fullyPopulateAccessibilityEvent(AccessibilityEvent sentEvent)723     private void fullyPopulateAccessibilityEvent(AccessibilityEvent sentEvent) {
724         sentEvent.setAddedCount(1);
725         sentEvent.setBeforeText("BeforeText");
726         sentEvent.setChecked(true);
727         sentEvent.setClassName("foo.bar.baz.Class");
728         sentEvent.setContentDescription("ContentDescription");
729         sentEvent.setCurrentItemIndex(1);
730         sentEvent.setEnabled(true);
731         sentEvent.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
732         sentEvent.setEventTime(1000);
733         sentEvent.setFromIndex(1);
734         sentEvent.setFullScreen(true);
735         sentEvent.setItemCount(1);
736         sentEvent.setPackageName("foo.bar.baz");
737         sentEvent.setParcelableData(Message.obtain(null, 1, 2, 3));
738         sentEvent.setPassword(true);
739         sentEvent.setRemovedCount(1);
740         sentEvent.getText().add("Foo");
741         sentEvent.setMaxScrollX(1);
742         sentEvent.setMaxScrollY(1);
743         sentEvent.setScrollX(1);
744         sentEvent.setScrollY(1);
745         sentEvent.setScrollDeltaX(3);
746         sentEvent.setScrollDeltaY(3);
747         sentEvent.setToIndex(1);
748         sentEvent.setScrollable(true);
749         sentEvent.setAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
750         sentEvent.setMovementGranularity(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
751         sentEvent.setDisplayId(Display.DEFAULT_DISPLAY);
752         sentEvent.setSpeechStateChangeTypes(AccessibilityEvent.SPEECH_STATE_SPEAKING_START);
753 
754         AccessibilityRecord record = AccessibilityRecord.obtain();
755         AccessibilityRecordTest.fullyPopulateAccessibilityRecord(record);
756         sentEvent.appendRecord(record);
757     }
758 
759     /**
760      * Compares all properties of the <code>expectedEvent</code> and the <code>receivedEvent</code>
761      * to verify that the received event is the one that is expected.
762      */
assertEqualsAccessibilityEvent( AccessibilityEvent expectedEvent, AccessibilityEvent receivedEvent)763     private static void assertEqualsAccessibilityEvent(
764             AccessibilityEvent expectedEvent, AccessibilityEvent receivedEvent) {
765         assertEquals(
766                 "addedCount has incorrect value",
767                 expectedEvent.getAddedCount(),
768                 receivedEvent.getAddedCount());
769         assertEquals(
770                 "beforeText has incorrect value",
771                 expectedEvent.getBeforeText(),
772                 receivedEvent.getBeforeText());
773         assertEquals(
774                 "checked has incorrect value",
775                 expectedEvent.isChecked(),
776                 receivedEvent.isChecked());
777         assertEquals(
778                 "className has incorrect value",
779                 expectedEvent.getClassName(),
780                 receivedEvent.getClassName());
781         assertEquals(
782                 "contentDescription has incorrect value",
783                 expectedEvent.getContentDescription(),
784                 receivedEvent.getContentDescription());
785         assertEquals(
786                 "currentItemIndex has incorrect value",
787                 expectedEvent.getCurrentItemIndex(),
788                 receivedEvent.getCurrentItemIndex());
789         assertEquals(
790                 "enabled has incorrect value",
791                 expectedEvent.isEnabled(),
792                 receivedEvent.isEnabled());
793         assertEquals(
794                 "eventType has incorrect value",
795                 expectedEvent.getEventType(),
796                 receivedEvent.getEventType());
797         assertEquals(
798                 "fromIndex has incorrect value",
799                 expectedEvent.getFromIndex(),
800                 receivedEvent.getFromIndex());
801         assertEquals(
802                 "fullScreen has incorrect value",
803                 expectedEvent.isFullScreen(),
804                 receivedEvent.isFullScreen());
805         assertEquals(
806                 "itemCount has incorrect value",
807                 expectedEvent.getItemCount(),
808                 receivedEvent.getItemCount());
809         assertEquals(
810                 "password has incorrect value",
811                 expectedEvent.isPassword(),
812                 receivedEvent.isPassword());
813         assertEquals(
814                 "removedCount has incorrect value",
815                 expectedEvent.getRemovedCount(),
816                 receivedEvent.getRemovedCount());
817         assertSame(
818                 "maxScrollX has incorrect value",
819                 expectedEvent.getMaxScrollX(),
820                 receivedEvent.getMaxScrollX());
821         assertSame(
822                 "maxScrollY has incorrect value",
823                 expectedEvent.getMaxScrollY(),
824                 receivedEvent.getMaxScrollY());
825         assertSame(
826                 "scrollX has incorrect value",
827                 expectedEvent.getScrollX(),
828                 receivedEvent.getScrollX());
829         assertSame(
830                 "scrollY has incorrect value",
831                 expectedEvent.getScrollY(),
832                 receivedEvent.getScrollY());
833         assertSame(
834                 "scrollDeltaX has incorrect value",
835                 expectedEvent.getScrollDeltaX(),
836                 receivedEvent.getScrollDeltaX());
837         assertSame(
838                 "scrollDeltaY has incorrect value",
839                 expectedEvent.getScrollDeltaY(),
840                 receivedEvent.getScrollDeltaY());
841         assertSame(
842                 "toIndex has incorrect value",
843                 expectedEvent.getToIndex(),
844                 receivedEvent.getToIndex());
845         assertSame(
846                 "scrollable has incorrect value",
847                 expectedEvent.isScrollable(),
848                 receivedEvent.isScrollable());
849         assertSame(
850                 "granularity has incorrect value",
851                 expectedEvent.getMovementGranularity(),
852                 receivedEvent.getMovementGranularity());
853         assertSame(
854                 "action has incorrect value", expectedEvent.getAction(), receivedEvent.getAction());
855         assertSame(
856                 "windowChangeTypes has incorrect value",
857                 expectedEvent.getWindowChanges(),
858                 receivedEvent.getWindowChanges());
859         assertEquals(
860                 "speechStateChangeTypes has incorrect value,",
861                 expectedEvent.getSpeechStateChangeTypes(),
862                 receivedEvent.getSpeechStateChangeTypes());
863 
864         AccessibilityRecordTest.assertEqualsText(expectedEvent.getText(), receivedEvent.getText());
865         AccessibilityRecordTest.assertEqualAccessibilityRecord(expectedEvent, receivedEvent);
866 
867         assertEqualAppendedRecord(expectedEvent, receivedEvent);
868     }
869 
assertEqualAppendedRecord( AccessibilityEvent expectedEvent, AccessibilityEvent receivedEvent)870     private static void assertEqualAppendedRecord(
871             AccessibilityEvent expectedEvent, AccessibilityEvent receivedEvent) {
872         assertEquals(
873                 "recordCount has incorrect value",
874                 expectedEvent.getRecordCount(),
875                 receivedEvent.getRecordCount());
876         if (expectedEvent.getRecordCount() != 0 && receivedEvent.getRecordCount() != 0) {
877             AccessibilityRecord expectedRecord = expectedEvent.getRecord(0);
878             AccessibilityRecord receivedRecord = receivedEvent.getRecord(0);
879             AccessibilityRecordTest.assertEqualAccessibilityRecord(expectedRecord, receivedRecord);
880         }
881     }
882 }
883