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