blob: 2b3e092aad1538b4a38f3cdc7d8008651c1c5f43 [file] [log] [blame]
/**
* Copyright (C) 2012 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.accessibilityservice.cts;
import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithAction;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.cts.activities.AccessibilityFocusAndInputFocusSyncActivity;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Point;
import android.os.Environment;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.BitmapUtils;
import com.android.compatibility.common.util.CddTest;
import com.android.compatibility.common.util.PollingCheck;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Test cases for testing the accessibility focus APIs exposed to accessibility
* services. These APIs allow moving accessibility focus in the view tree from
* an AccessiiblityService. Specifically, this activity is for verifying the the
* sync between accessibility and input focus.
*/
@RunWith(AndroidJUnit4.class)
@CddTest(requirements = {"3.10/C-1-1,C-1-2"})
@Presubmit
public class AccessibilityFocusAndInputFocusSyncTest {
/**
* The delay time is for next UI frame rendering out.
*/
private static final long SCREEN_FRAME_RENDERING_OUT_TIME_MILLIS = 500;
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
private static Context sContext;
private static AccessibilityManager sAccessibilityManager;
private static int sFocusStrokeWidthDefaultValue;
private static int sFocusColorDefaultValue;
private AccessibilityFocusAndInputFocusSyncActivity mActivity;
private ActivityTestRule<AccessibilityFocusAndInputFocusSyncActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityFocusAndInputFocusSyncActivity.class, false, false);
private InstrumentedAccessibilityServiceTestRule<StubFocusIndicatorService>
mFocusIndicatorServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
StubFocusIndicatorService.class, false);
private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
new AccessibilityDumpOnFailureRule();
@Rule
public final RuleChain mRuleChain = RuleChain
.outerRule(mActivityRule)
.around(mFocusIndicatorServiceRule)
.around(mDumpOnFailureRule);
/* Test name rule that tracks the current test method under execution */
@Rule public TestName mTestName = new TestName();
@BeforeClass
public static void oneTimeSetup() throws Exception {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
sUiAutomation = sInstrumentation.getUiAutomation(
UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
sContext = sInstrumentation.getContext();
sAccessibilityManager = sContext.getSystemService(AccessibilityManager.class);
assertNotNull(sAccessibilityManager);
sFocusStrokeWidthDefaultValue = sAccessibilityManager.getAccessibilityFocusStrokeWidth();
sFocusColorDefaultValue = sAccessibilityManager.getAccessibilityFocusColor();
}
@AfterClass
public static void postTestTearDown() {
sUiAutomation.destroy();
}
@Before
public void setUp() throws Exception {
AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
info.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
sUiAutomation.setServiceInfo(info);
mActivity = launchActivityAndWaitForItToBeOnscreen(
sInstrumentation, sUiAutomation, mActivityRule);
}
@MediumTest
@Test
public void testFindAccessibilityFocus() throws Exception {
sInstrumentation.runOnMainSync(() -> {
mActivity.findViewById(R.id.firstEditText).requestFocus();
});
// Get the view that has input and accessibility focus.
final AccessibilityNodeInfo expected = sUiAutomation
.getRootInActiveWindow().findAccessibilityNodeInfosByText(
sContext.getString(R.string.firstEditText)).get(0);
assertNotNull(expected);
assertFalse(expected.isAccessibilityFocused());
assertTrue(expected.isFocused());
sUiAutomation.executeAndWaitForEvent(
() -> assertTrue(expected.performAction(ACTION_ACCESSIBILITY_FOCUS)),
filterForEventTypeWithAction(
TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
DEFAULT_TIMEOUT_MS);
// Get the second expected node info.
AccessibilityNodeInfo received = sUiAutomation
.getRootInActiveWindow().findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
assertNotNull(received);
assertTrue(received.isAccessibilityFocused());
// Make sure we got the expected focusable.
assertEquals(expected, received);
}
@MediumTest
@Test
public void testInitialStateNoAccessibilityFocus() throws Exception {
// Get the root which is only accessibility focused.
AccessibilityNodeInfo focused = sUiAutomation
.getRootInActiveWindow().findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
assertNull(focused);
}
@MediumTest
@Test
public void testActionAccessibilityFocus() throws Exception {
// Get the root linear layout info.
final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
.getRootInActiveWindow().findAccessibilityNodeInfosByText(
sContext.getString(R.string.rootLinearLayout)).get(0);
assertNotNull(rootLinearLayout);
assertFalse(rootLinearLayout.isAccessibilityFocused());
sUiAutomation.executeAndWaitForEvent(
() -> assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS)),
filterForEventTypeWithAction(
TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
DEFAULT_TIMEOUT_MS);
// Get the node info again.
rootLinearLayout.refresh();
// Check if the node info is focused.
assertTrue(rootLinearLayout.isAccessibilityFocused());
}
@MediumTest
@Test
public void testActionClearAccessibilityFocus() throws Exception {
// Get the root linear layout info.
final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
.getRootInActiveWindow().findAccessibilityNodeInfosByText(
sContext.getString(R.string.rootLinearLayout)).get(0);
assertNotNull(rootLinearLayout);
sUiAutomation.executeAndWaitForEvent(
() -> assertTrue(rootLinearLayout.performAction(ACTION_ACCESSIBILITY_FOCUS)),
filterForEventTypeWithAction(
TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
DEFAULT_TIMEOUT_MS);
// Refresh the node info.
rootLinearLayout.refresh();
// Check if the node info is focused.
assertTrue(rootLinearLayout.isAccessibilityFocused());
sUiAutomation.executeAndWaitForEvent(
() -> assertTrue(rootLinearLayout.performAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS)),
filterForEventTypeWithAction(
TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, ACTION_CLEAR_ACCESSIBILITY_FOCUS),
DEFAULT_TIMEOUT_MS);
// Refresh the node info.
rootLinearLayout.refresh();
// Check if the node info is not focused.
assertFalse(rootLinearLayout.isAccessibilityFocused());
}
@MediumTest
@Test
public void testOnlyOneNodeHasAccessibilityFocus() throws Exception {
// Get the first not focused edit text.
final AccessibilityNodeInfo firstEditText = sUiAutomation
.getRootInActiveWindow().findAccessibilityNodeInfosByText(
sContext.getString(R.string.firstEditText)).get(0);
assertNotNull(firstEditText);
assertTrue(firstEditText.isFocusable());
assertFalse(firstEditText.isAccessibilityFocused());
sUiAutomation.executeAndWaitForEvent(
() -> assertTrue(firstEditText.performAction(ACTION_ACCESSIBILITY_FOCUS)),
filterForEventTypeWithAction(
TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
DEFAULT_TIMEOUT_MS);
// Get the second not focused edit text.
final AccessibilityNodeInfo secondEditText = sUiAutomation
.getRootInActiveWindow().findAccessibilityNodeInfosByText(
sContext.getString(R.string.secondEditText)).get(0);
assertNotNull(secondEditText);
assertTrue(secondEditText.isFocusable());
assertFalse(secondEditText.isFocused());
assertFalse(secondEditText.isAccessibilityFocused());
sUiAutomation.executeAndWaitForEvent(
() -> assertTrue(secondEditText.performAction(ACTION_ACCESSIBILITY_FOCUS)),
filterForEventTypeWithAction(
TYPE_VIEW_ACCESSIBILITY_FOCUSED, ACTION_ACCESSIBILITY_FOCUS),
DEFAULT_TIMEOUT_MS);
// Get the node info again.
secondEditText.refresh();
// Make sure no other node has accessibility focus.
AccessibilityNodeInfo root = sUiAutomation.getRootInActiveWindow();
Queue<AccessibilityNodeInfo> workQueue = new LinkedList<AccessibilityNodeInfo>();
workQueue.add(root);
while (!workQueue.isEmpty()) {
AccessibilityNodeInfo current = workQueue.poll();
if (current.isAccessibilityFocused() && !current.equals(secondEditText)) {
fail();
}
final int childCount = current.getChildCount();
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo child = current.getChild(i);
if (child != null) {
workQueue.offer(child);
}
}
}
}
@Test
public void testScreenReaderFocusableAttribute_reportedToAccessibility() {
final AccessibilityNodeInfo secondButton = sUiAutomation.getRootInActiveWindow()
.findAccessibilityNodeInfosByText(
sContext.getString(R.string.secondButton)).get(0);
assertTrue("Screen reader focusability not propagated from xml to accessibility",
secondButton.isScreenReaderFocusable());
// Verify the setter and getter work
final AtomicBoolean isScreenReaderFocusableAtomic = new AtomicBoolean(false);
sInstrumentation.runOnMainSync(() -> {
View secondButtonView = mActivity.findViewById(R.id.secondButton);
secondButtonView.setScreenReaderFocusable(false);
isScreenReaderFocusableAtomic.set(secondButtonView.isScreenReaderFocusable());
});
assertFalse("isScreenReaderFocusable did not change after value set",
isScreenReaderFocusableAtomic.get());
secondButton.refresh();
assertFalse(
"Screen reader focusability not propagated to accessibility after calling setter",
secondButton.isScreenReaderFocusable());
}
@Test
public void testSetFocusAppearanceDataAfterServiceEnabled() {
final StubFocusIndicatorService service =
mFocusIndicatorServiceRule.enableService();
final int focusColor = sFocusColorDefaultValue == Color.BLUE ? Color.RED : Color.BLUE;
try {
setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue + 10,
focusColor);
} finally {
setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue,
sFocusColorDefaultValue);
service.disableSelfAndRemove();
}
}
@Test
public void testChangeFocusColor_expectedColorIsChanged() throws Exception {
final StubFocusIndicatorService service =
mFocusIndicatorServiceRule.enableService();
try {
// Get the root linear layout info.
final AccessibilityNodeInfo rootLinearLayout = sUiAutomation
.getRootInActiveWindow().findAccessibilityNodeInfosByText(
sContext.getString(R.string.rootLinearLayout)).get(0);
final Bitmap blueColorFocusScreenshot = screenshotAfterChangeFocusColor(service,
rootLinearLayout, Color.BLUE);
final Bitmap redColorFocusScreenshot = screenshotAfterChangeFocusColor(service,
rootLinearLayout, Color.RED);
assertTrue(isBitmapDifferent(blueColorFocusScreenshot, redColorFocusScreenshot));
} finally {
setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue,
sFocusColorDefaultValue);
service.disableSelfAndRemove();
}
}
private Bitmap screenshotAfterChangeFocusColor(StubFocusIndicatorService service,
AccessibilityNodeInfo unAccessibilityFocusedNode, int color) throws Exception {
assertFalse(unAccessibilityFocusedNode.isAccessibilityFocused());
setFocusAppearanceDataAndCheckItCorrect(service, sFocusStrokeWidthDefaultValue, color);
sUiAutomation.executeAndWaitForEvent(
() -> assertTrue(unAccessibilityFocusedNode.performAction(
ACTION_ACCESSIBILITY_FOCUS)),
filterForEventTypeWithAction(TYPE_VIEW_ACCESSIBILITY_FOCUSED,
ACTION_ACCESSIBILITY_FOCUS),
DEFAULT_TIMEOUT_MS);
Thread.sleep(SCREEN_FRAME_RENDERING_OUT_TIME_MILLIS);
final Bitmap screenshot = sUiAutomation.takeScreenshot();
sUiAutomation.executeAndWaitForEvent(
() -> assertTrue(unAccessibilityFocusedNode.performAction(
ACTION_CLEAR_ACCESSIBILITY_FOCUS)),
filterForEventTypeWithAction(TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
ACTION_CLEAR_ACCESSIBILITY_FOCUS),
DEFAULT_TIMEOUT_MS);
return screenshot;
}
private boolean isBitmapDifferent(Bitmap bitmap1, Bitmap bitmap2) {
final Display display = mActivity.getWindowManager().getDefaultDisplay();
final Point displaySize = new Point();
display.getRealSize(displaySize);
final int[] pixelsOne = new int[displaySize.x * displaySize.y];
final Bitmap bitmapOne = bitmap1.copy(Bitmap.Config.ARGB_8888, false);
bitmapOne.getPixels(pixelsOne, 0, displaySize.x, 0, 0, displaySize.x,
displaySize.y);
final int[] pixelsTwo = new int[displaySize.x * displaySize.y];
final Bitmap bitmapTwo = bitmap2.copy(Bitmap.Config.ARGB_8888, false);
bitmapTwo.getPixels(pixelsTwo, 0, displaySize.x, 0, 0, displaySize.x,
displaySize.y);
for (int i = pixelsOne.length - 1; i > 0; i--) {
if ((Color.red(pixelsOne[i]) != Color.red(pixelsTwo[i]))
|| (Color.green(pixelsOne[i]) != Color.green(pixelsTwo[i]))
|| (Color.blue(pixelsOne[i]) != Color.blue(pixelsTwo[i]))) {
return true;
}
}
saveFailureScreenshot(bitmap1, bitmap2);
return false;
}
private void saveFailureScreenshot(Bitmap bitmap1, Bitmap bitmap2) {
final String directoryName = Environment.getExternalStorageDirectory()
+ "/" + getClass().getSimpleName();
final String fileName1 = String.format("%s_%s_%s.png", mTestName.getMethodName(), "Bitmap1",
SystemClock.uptimeMillis());
BitmapUtils.saveBitmap(bitmap1, directoryName, fileName1);
final String fileName2 = String.format("%s_%s_%s.png", mTestName.getMethodName(), "Bitmap2",
SystemClock.uptimeMillis());
BitmapUtils.saveBitmap(bitmap2, directoryName, fileName2);
}
private void setFocusAppearanceDataAndCheckItCorrect(StubFocusIndicatorService service,
int focusStrokeWidthValue, int focusColorValue) {
service.setAccessibilityFocusAppearance(focusStrokeWidthValue,
focusColorValue);
// Checks if the color and the stroke values from AccessibilityManager is
// updated as in expectation.
PollingCheck.waitFor(()->isFocusAppearanceDataUpdated(sAccessibilityManager,
focusStrokeWidthValue, focusColorValue));
}
private static boolean isFocusAppearanceDataUpdated(AccessibilityManager manager,
int strokeWidth, int color) {
return manager.getAccessibilityFocusStrokeWidth() == strokeWidth
&& manager.getAccessibilityFocusColor() == color;
}
}