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