blob: 8143a82ea6fdccd5e97d6081c8c2a58bc785c6dd [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view.accessibility.cts;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.app.Activity;
import android.content.Context;
import android.os.Message;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.text.TextUtils;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityRecord;
import android.view.accessibility.cts.SpeakingAccessibilityService;
import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import junit.framework.TestCase;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.List;
import static org.junit.Assert.*;
/**
* Class for testing {@link AccessibilityEvent}.
*/
@Presubmit
@RunWith(AndroidJUnit4.class)
public class AccessibilityEventTest {
private EventReportingLinearLayout mParentView;
private View mChildView;
private AccessibilityManager mAccessibilityManager;
private final ActivityTestRule<DummyActivity> mActivityRule =
new ActivityTestRule<>(DummyActivity.class, false, false);
private final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
new AccessibilityDumpOnFailureRule();
private InstrumentedAccessibilityServiceTestRule<SpeakingAccessibilityService>
mInstrumentedAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
SpeakingAccessibilityService.class, false);
@Rule
public final RuleChain mRuleChain = RuleChain
.outerRule(mActivityRule)
.around(mInstrumentedAccessibilityServiceRule)
.around(mDumpOnFailureRule);
@Before
public void setUp() throws Throwable {
final Activity activity = mActivityRule.launchActivity(null);
mAccessibilityManager = activity.getSystemService(AccessibilityManager.class);
mInstrumentedAccessibilityServiceRule.enableService();
mActivityRule.runOnUiThread(() -> {
final LinearLayout grandparent = new LinearLayout(activity);
activity.setContentView(grandparent);
mParentView = new EventReportingLinearLayout(activity);
mChildView = new View(activity);
grandparent.addView(mParentView);
mParentView.addView(mChildView);
});
}
private static class EventReportingLinearLayout extends LinearLayout {
public List<AccessibilityEvent> mReceivedEvents = new ArrayList<AccessibilityEvent>();
public EventReportingLinearLayout(Context context) {
super(context);
}
@Override
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
mReceivedEvents.add(AccessibilityEvent.obtain(event));
return super.requestSendAccessibilityEvent(child, event);
}
}
@Test
public void testScrollEvent() throws Exception {
mChildView.scrollTo(0,100);
Thread.sleep(1000);
scrollEventFilter.assertReceivedEventCount(1);
}
@Test
public void testScrollEventBurstCombined() throws Exception {
mChildView.scrollTo(0, 100);
mChildView.scrollTo(0, 125);
mChildView.scrollTo(0, 150);
mChildView.scrollTo(0, 175);
Thread.sleep(1000);
scrollEventFilter.assertReceivedEventCount(1);
}
@Test
public void testScrollEventsDeliveredInCorrectInterval() throws Exception {
mChildView.scrollTo(0, 25);
mChildView.scrollTo(0, 50);
mChildView.scrollTo(0, 100);
Thread.sleep(150);
mChildView.scrollTo(0, 150);
mChildView.scrollTo(0, 175);
Thread.sleep(50);
mChildView.scrollTo(0, 200);
Thread.sleep(1000);
scrollEventFilter.assertReceivedEventCount(2);
}
private AccessibilityEventFilter scrollEventFilter = new AccessibilityEventFilter() {
public boolean pass(AccessibilityEvent event) {
return event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED;
}
};
@Test
public void testScrollEventsClearedOnDetach() throws Throwable {
mChildView.scrollTo(0, 25);
mChildView.scrollTo(5, 50);
mChildView.scrollTo(7, 100);
mActivityRule.runOnUiThread(() -> {
mParentView.removeView(mChildView);
mParentView.addView(mChildView);
});
mChildView.scrollTo(0, 150);
Thread.sleep(1000);
AccessibilityEvent event = scrollEventFilter.getLastEvent();
assertEquals(-7, event.getScrollDeltaX());
assertEquals(50, event.getScrollDeltaY());
}
@Test
public void testScrollEventsCaptureTotalDelta() throws Throwable {
mChildView.scrollTo(0, 25);
mChildView.scrollTo(5, 50);
mChildView.scrollTo(7, 100);
Thread.sleep(1000);
AccessibilityEvent event = scrollEventFilter.getLastEvent();
assertEquals(7, event.getScrollDeltaX());
assertEquals(100, event.getScrollDeltaY());
}
@Test
public void testScrollEventsClearDeltaAfterSending() throws Throwable {
mChildView.scrollTo(0, 25);
mChildView.scrollTo(5, 50);
mChildView.scrollTo(7, 100);
Thread.sleep(1000);
mChildView.scrollTo(0, 150);
Thread.sleep(1000);
AccessibilityEvent event = scrollEventFilter.getLastEvent();
assertEquals(-7, event.getScrollDeltaX());
assertEquals(50, event.getScrollDeltaY());
}
@Test
public void testStateEvent() throws Throwable {
sendStateDescriptionChangedEvent(mChildView);
Thread.sleep(1000);
stateDescriptionEventFilter.assertReceivedEventCount(1);
}
@Test
public void testStateEventBurstCombined() throws Throwable {
sendStateDescriptionChangedEvent(mChildView);
sendStateDescriptionChangedEvent(mChildView);
sendStateDescriptionChangedEvent(mChildView);
sendStateDescriptionChangedEvent(mChildView);
Thread.sleep(1000);
stateDescriptionEventFilter.assertReceivedEventCount(1);
}
@Test
public void testStateEventsDeliveredInCorrectInterval() throws Throwable {
sendStateDescriptionChangedEvent(mChildView);
sendStateDescriptionChangedEvent(mChildView);
sendStateDescriptionChangedEvent(mChildView);
Thread.sleep(150);
sendStateDescriptionChangedEvent(mChildView);
sendStateDescriptionChangedEvent(mChildView);
Thread.sleep(50);
sendStateDescriptionChangedEvent(mChildView);
Thread.sleep(1000);
stateDescriptionEventFilter.assertReceivedEventCount(2);
}
@Test
public void testStateEventsHaveLastEventText() throws Throwable {
sendStateDescriptionChangedEvent(mChildView, "First state");
String expectedState = "Second state";
sendStateDescriptionChangedEvent(mChildView, expectedState);
Thread.sleep(1000);
AccessibilityEvent event = stateDescriptionEventFilter.getLastEvent();
assertEquals(expectedState, event.getText().get(0));
}
private AccessibilityEventFilter stateDescriptionEventFilter = new AccessibilityEventFilter() {
public boolean pass(AccessibilityEvent event) {
return event.getContentChangeTypes() == AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION;
}
};
private abstract class AccessibilityEventFilter {
abstract boolean pass(AccessibilityEvent event);
void assertReceivedEventCount(int count) {
assertEquals(count, filteredEventsReceived().size());
}
AccessibilityEvent getLastEvent() {
List<AccessibilityEvent> events = filteredEventsReceived();
if (events.size() > 0) {
return events.get(events.size() - 1);
}
return null;
}
private List<AccessibilityEvent> filteredEventsReceived() {
List<AccessibilityEvent> filteredEvents = new ArrayList<AccessibilityEvent>();
List<AccessibilityEvent> receivedEvents = mParentView.mReceivedEvents;
for (int i = 0; i < receivedEvents.size(); i++) {
if (pass(receivedEvents.get(i))) {
filteredEvents.add(receivedEvents.get(i));
}
}
return filteredEvents;
}
}
private void sendStateDescriptionChangedEvent(View view) {
sendStateDescriptionChangedEvent(view, null);
}
private void sendStateDescriptionChangedEvent(View view, CharSequence text) {
AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
event.setContentChangeTypes(AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION);
event.getText().add(text);
view.sendAccessibilityEventUnchecked(event);
}
/**
* Tests whether accessibility events are correctly written and
* read from a parcel (version 1).
*/
@SmallTest
@Test
public void testMarshaling() throws Exception {
// fully populate the event to marshal
AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
fullyPopulateAccessibilityEvent(sentEvent);
// marshal and unmarshal the event
Parcel parcel = Parcel.obtain();
sentEvent.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
AccessibilityEvent receivedEvent = AccessibilityEvent.CREATOR.createFromParcel(parcel);
// make sure all fields properly marshaled
assertEqualsAccessiblityEvent(sentEvent, receivedEvent);
parcel.recycle();
}
/**
* Tests if {@link AccessibilityEvent} are properly reused.
*/
@SmallTest
@Test
public void testReuse() {
AccessibilityEvent firstEvent = AccessibilityEvent.obtain();
firstEvent.recycle();
AccessibilityEvent secondEvent = AccessibilityEvent.obtain();
assertSame("AccessibilityEvent not properly reused", firstEvent, secondEvent);
}
/**
* Tests if {@link AccessibilityEvent} are properly recycled.
*/
@SmallTest
@Test
public void testRecycle() {
// obtain and populate an event
AccessibilityEvent populatedEvent = AccessibilityEvent.obtain();
fullyPopulateAccessibilityEvent(populatedEvent);
// recycle and obtain the same recycled instance
populatedEvent.recycle();
AccessibilityEvent recycledEvent = AccessibilityEvent.obtain();
// check expectations
assertAccessibilityEventCleared(recycledEvent);
}
/**
* Tests whether the event types are correctly converted to strings.
*/
@SmallTest
@Test
public void testEventTypeToString() {
assertEquals("TYPE_NOTIFICATION_STATE_CHANGED", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED));
assertEquals("TYPE_TOUCH_EXPLORATION_GESTURE_END", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END));
assertEquals("TYPE_TOUCH_EXPLORATION_GESTURE_START", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START));
assertEquals("TYPE_VIEW_CLICKED", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_VIEW_CLICKED));
assertEquals("TYPE_VIEW_FOCUSED", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_VIEW_FOCUSED));
assertEquals("TYPE_VIEW_HOVER_ENTER",
AccessibilityEvent.eventTypeToString(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER));
assertEquals("TYPE_VIEW_HOVER_EXIT", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_VIEW_HOVER_EXIT));
assertEquals("TYPE_VIEW_LONG_CLICKED", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_VIEW_LONG_CLICKED));
assertEquals("TYPE_VIEW_CONTEXT_CLICKED", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_VIEW_CONTEXT_CLICKED));
assertEquals("TYPE_VIEW_SCROLLED", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_VIEW_SCROLLED));
assertEquals("TYPE_VIEW_SELECTED", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_VIEW_SELECTED));
assertEquals("TYPE_VIEW_TEXT_CHANGED", AccessibilityEvent.eventTypeToString(
AccessibilityEvent .TYPE_VIEW_TEXT_CHANGED));
assertEquals("TYPE_VIEW_TEXT_SELECTION_CHANGED", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED));
assertEquals("TYPE_WINDOW_CONTENT_CHANGED", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED));
assertEquals("TYPE_WINDOW_STATE_CHANGED", AccessibilityEvent.eventTypeToString(
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED));
}
/**
* Tests whether the event describes its contents consistently.
*/
@SmallTest
@Test
public void testDescribeContents() {
AccessibilityEvent event = AccessibilityEvent.obtain();
assertSame("Accessibility events always return 0 for this method.", 0,
event.describeContents());
fullyPopulateAccessibilityEvent(event);
assertSame("Accessibility events always return 0 for this method.", 0,
event.describeContents());
}
/**
* Tests whether accessibility events are correctly written and
* read from a parcel (version 2).
*/
@SmallTest
@Test
public void testMarshaling2() {
AccessibilityEvent marshaledEvent = AccessibilityEvent.obtain();
fullyPopulateAccessibilityEvent(marshaledEvent);
Parcel parcel = Parcel.obtain();
marshaledEvent.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
AccessibilityEvent unmarshaledEvent = AccessibilityEvent.obtain();
unmarshaledEvent.initFromParcel(parcel);
assertEqualsAccessiblityEvent(marshaledEvent, unmarshaledEvent);
parcel.recycle();
}
/**
* While CharSequence is immutable, some classes implementing it are mutable. Make sure they
* can't change the object by changing the objects backing CharSequence
*/
@SmallTest
@Test
public void testChangeTextAfterSetting_shouldNotAffectEvent() {
final String originalText = "Cassowary";
final String newText = "Hornbill";
AccessibilityEvent event = AccessibilityEvent.obtain();
StringBuffer updatingString = new StringBuffer(originalText);
event.setBeforeText(updatingString);
event.setContentDescription(updatingString);
updatingString.delete(0, updatingString.length());
updatingString.append(newText);
assertTrue(TextUtils.equals(originalText, event.getBeforeText()));
assertTrue(TextUtils.equals(originalText, event.getContentDescription()));
}
/**
* Fully populates the {@link AccessibilityEvent} to marshal.
*
* @param sentEvent The event to populate.
*/
private void fullyPopulateAccessibilityEvent(AccessibilityEvent sentEvent) {
sentEvent.setAddedCount(1);
sentEvent.setBeforeText("BeforeText");
sentEvent.setChecked(true);
sentEvent.setClassName("foo.bar.baz.Class");
sentEvent.setContentDescription("ContentDescription");
sentEvent.setCurrentItemIndex(1);
sentEvent.setEnabled(true);
sentEvent.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
sentEvent.setEventTime(1000);
sentEvent.setFromIndex(1);
sentEvent.setFullScreen(true);
sentEvent.setItemCount(1);
sentEvent.setPackageName("foo.bar.baz");
sentEvent.setParcelableData(Message.obtain(null, 1, 2, 3));
sentEvent.setPassword(true);
sentEvent.setRemovedCount(1);
sentEvent.getText().add("Foo");
sentEvent.setMaxScrollX(1);
sentEvent.setMaxScrollY(1);
sentEvent.setScrollX(1);
sentEvent.setScrollY(1);
sentEvent.setScrollDeltaX(3);
sentEvent.setScrollDeltaY(3);
sentEvent.setToIndex(1);
sentEvent.setScrollable(true);
sentEvent.setAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
sentEvent.setMovementGranularity(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
AccessibilityRecord record = AccessibilityRecord.obtain();
AccessibilityRecordTest.fullyPopulateAccessibilityRecord(record);
sentEvent.appendRecord(record);
}
/**
* Compares all properties of the <code>expectedEvent</code> and the
* <code>receviedEvent</code> to verify that the received event is the one
* that is expected.
*/
private static void assertEqualsAccessiblityEvent(AccessibilityEvent expectedEvent,
AccessibilityEvent receivedEvent) {
assertEquals("addedCount has incorrect value", expectedEvent.getAddedCount(), receivedEvent
.getAddedCount());
assertEquals("beforeText has incorrect value", expectedEvent.getBeforeText(), receivedEvent
.getBeforeText());
assertEquals("checked has incorrect value", expectedEvent.isChecked(), receivedEvent
.isChecked());
assertEquals("className has incorrect value", expectedEvent.getClassName(), receivedEvent
.getClassName());
assertEquals("contentDescription has incorrect value", expectedEvent
.getContentDescription(), receivedEvent.getContentDescription());
assertEquals("currentItemIndex has incorrect value", expectedEvent.getCurrentItemIndex(),
receivedEvent.getCurrentItemIndex());
assertEquals("enabled has incorrect value", expectedEvent.isEnabled(), receivedEvent
.isEnabled());
assertEquals("eventType has incorrect value", expectedEvent.getEventType(), receivedEvent
.getEventType());
assertEquals("fromIndex has incorrect value", expectedEvent.getFromIndex(), receivedEvent
.getFromIndex());
assertEquals("fullScreen has incorrect value", expectedEvent.isFullScreen(), receivedEvent
.isFullScreen());
assertEquals("itemCount has incorrect value", expectedEvent.getItemCount(), receivedEvent
.getItemCount());
assertEquals("password has incorrect value", expectedEvent.isPassword(), receivedEvent
.isPassword());
assertEquals("removedCount has incorrect value", expectedEvent.getRemovedCount(),
receivedEvent.getRemovedCount());
AccessibilityRecordTest.assertEqualsText(expectedEvent.getText(), receivedEvent.getText());
assertEquals("must have one record", expectedEvent.getRecordCount(),
receivedEvent.getRecordCount());
assertSame("maxScrollX has incorect value", expectedEvent.getMaxScrollX(),
receivedEvent.getMaxScrollX());
assertSame("maxScrollY has incorect value", expectedEvent.getMaxScrollY(),
receivedEvent.getMaxScrollY());
assertSame("scrollX has incorect value", expectedEvent.getScrollX(),
receivedEvent.getScrollX());
assertSame("scrollY has incorect value", expectedEvent.getScrollY(),
receivedEvent.getScrollY());
assertSame("scrollDeltaX has incorect value", expectedEvent.getScrollDeltaX(),
receivedEvent.getScrollDeltaX());
assertSame("scrollDeltaY has incorect value", expectedEvent.getScrollDeltaY(),
receivedEvent.getScrollDeltaY());
assertSame("toIndex has incorect value", expectedEvent.getToIndex(),
receivedEvent.getToIndex());
assertSame("scrollable has incorect value", expectedEvent.isScrollable(),
receivedEvent.isScrollable());
assertSame("granularity has incorect value", expectedEvent.getMovementGranularity(),
receivedEvent.getMovementGranularity());
assertSame("action has incorect value", expectedEvent.getAction(),
receivedEvent.getAction());
assertSame("windowChangeTypes has incorect value", expectedEvent.getWindowChanges(),
receivedEvent.getWindowChanges());
assertSame("parcelableData has incorect value",
((Message) expectedEvent.getParcelableData()).what,
((Message) receivedEvent.getParcelableData()).what);
AccessibilityRecord receivedRecord = receivedEvent.getRecord(0);
AccessibilityRecordTest.assertEqualAccessibilityRecord(expectedEvent, receivedRecord);
}
/**
* Asserts that an {@link AccessibilityEvent} is cleared.
*
* @param event The event to check.
*/
private static void assertAccessibilityEventCleared(AccessibilityEvent event) {
AccessibilityRecordTest.assertAccessibilityRecordCleared(event);
TestCase.assertEquals("eventTime not properly recycled", 0, event.getEventTime());
TestCase.assertEquals("eventType not properly recycled", 0, event.getEventType());
TestCase.assertNull("packageName not properly recycled", event.getPackageName());
}
}