blob: 4d84a739de4ca01bcec9dd6c1aa85e4b7a5bd3d7 [file] [log] [blame]
/*
* Copyright (C) 2020 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.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.cts.activities.AccessibilityTestActivity;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import androidx.test.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
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.runner.RunWith;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Tests that AccessibilityNodeInfos from an embedded hierarchy that is present to another
* hierarchy are properly populated.
*/
@RunWith(AndroidJUnit4.class)
public class AccessibilityEmbeddedHierarchyTest {
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
private final ActivityTestRule<AccessibilityEmbeddedHierarchyActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityEmbeddedHierarchyActivity.class, false, false);
private static final String HOST_VIEW_RESOURCE_NAME =
"android.accessibilityservice.cts:id/host_surfaceview";
private static final String EMBEDDED_VIEW_RESOURCE_NAME =
"android.accessibilityservice.cts:id/embedded_button";
private final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
new AccessibilityDumpOnFailureRule();
private AccessibilityEmbeddedHierarchyActivity mActivity;
@Rule
public final RuleChain mRuleChain = RuleChain
.outerRule(mActivityRule)
.around(mDumpOnFailureRule);
@BeforeClass
public static void oneTimeSetup() {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
sUiAutomation = sInstrumentation.getUiAutomation();
AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
sUiAutomation.setServiceInfo(info);
}
@AfterClass
public static void postTestTearDown() {
sUiAutomation.destroy();
}
@Before
public void setUp() throws Throwable {
mActivity = launchActivityAndWaitForItToBeOnscreen(sInstrumentation, sUiAutomation,
mActivityRule);
mActivity.waitForEmbeddedHierarchy();
}
@Test
public void testEmbeddedViewCanBeFound() {
final AccessibilityNodeInfo target =
findEmbeddedAccessibilityNodeInfo(sUiAutomation.getRootInActiveWindow());
assertNotNull(target);
}
@Test
public void testEmbeddedViewCanFindItsHostParent() {
final AccessibilityNodeInfo target =
findEmbeddedAccessibilityNodeInfo(sUiAutomation.getRootInActiveWindow());
final AccessibilityNodeInfo parent = target.getParent();
assertTrue(HOST_VIEW_RESOURCE_NAME.equals(parent.getViewIdResourceName()));
}
@Test
public void testEmbeddedViewHasCorrectBound() {
final AccessibilityNodeInfo target =
findEmbeddedAccessibilityNodeInfo(sUiAutomation.getRootInActiveWindow());
final AccessibilityNodeInfo parent = target.getParent();
final Rect hostViewBoundsInScreen = new Rect();
final Rect embeddedViewBoundsInScreen = new Rect();
parent.getBoundsInScreen(hostViewBoundsInScreen);
target.getBoundsInScreen(embeddedViewBoundsInScreen);
assertTrue("hostViewBoundsInScreen" + hostViewBoundsInScreen.toShortString()
+ " doesn't contain embeddedViewBoundsInScreen"
+ embeddedViewBoundsInScreen.toShortString(),
hostViewBoundsInScreen.contains(embeddedViewBoundsInScreen));
}
@Test
public void testEmbeddedViewHasCorrectBoundAfterHostViewMove() throws TimeoutException {
final AccessibilityNodeInfo target =
findEmbeddedAccessibilityNodeInfo(sUiAutomation.getRootInActiveWindow());
final Rect hostViewBoundsInScreen = new Rect();
final Rect newEmbeddedViewBoundsInScreen = new Rect();
final Rect oldEmbeddedViewBoundsInScreen = new Rect();
target.getBoundsInScreen(oldEmbeddedViewBoundsInScreen);
// Move Host SurfaceView from (0, 0) to (50, 50).
mActivity.requestNewLayoutForTest(50, 50);
target.refresh();
final AccessibilityNodeInfo parent = target.getParent();
target.getBoundsInScreen(newEmbeddedViewBoundsInScreen);
parent.getBoundsInScreen(hostViewBoundsInScreen);
assertTrue("hostViewBoundsInScreen" + hostViewBoundsInScreen.toShortString()
+ " doesn't contain newEmbeddedViewBoundsInScreen"
+ newEmbeddedViewBoundsInScreen.toShortString(),
hostViewBoundsInScreen.contains(newEmbeddedViewBoundsInScreen));
assertFalse("newEmbeddedViewBoundsInScreen" + newEmbeddedViewBoundsInScreen.toShortString()
+ " shouldn't be the same with oldEmbeddedViewBoundsInScreen"
+ oldEmbeddedViewBoundsInScreen.toShortString(),
newEmbeddedViewBoundsInScreen.equals(oldEmbeddedViewBoundsInScreen));
}
@Test
public void testEmbeddedViewIsInvisibleAfterMovingOutOfScreen() throws TimeoutException {
final AccessibilityNodeInfo target =
findEmbeddedAccessibilityNodeInfo(sUiAutomation.getRootInActiveWindow());
assertTrue("Embedded view should be visible at beginning.",
target.isVisibleToUser());
// Move Host SurfaceView out of screen
final Point screenSize = getScreenSize();
mActivity.requestNewLayoutForTest(screenSize.x, screenSize.y);
target.refresh();
assertFalse("Embedded view should be invisible after moving out of screen.",
target.isVisibleToUser());
}
private AccessibilityNodeInfo findEmbeddedAccessibilityNodeInfo(AccessibilityNodeInfo root) {
final int childCount = root.getChildCount();
for (int i = 0; i < childCount; i++) {
final AccessibilityNodeInfo info = root.getChild(i);
if (info == null) {
continue;
}
if (EMBEDDED_VIEW_RESOURCE_NAME.equals(info.getViewIdResourceName())) {
return info;
}
if (info.getChildCount() != 0) {
return findEmbeddedAccessibilityNodeInfo(info);
}
}
return null;
}
private Point getScreenSize() {
final DisplayManager dm = sInstrumentation.getContext().getSystemService(
DisplayManager.class);
final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
final DisplayMetrics metrics = new DisplayMetrics();
display.getRealMetrics(metrics);
return new Point(metrics.widthPixels, metrics.heightPixels);
}
/**
* This class is an dummy {@link android.app.Activity} used to perform embedded hierarchy
* testing of the accessibility feature by interaction with the UI widgets.
*/
public static class AccessibilityEmbeddedHierarchyActivity extends
AccessibilityTestActivity implements SurfaceHolder.Callback {
private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
private static final int DEFAULT_WIDTH = 150;
private static final int DEFAULT_HEIGHT = 150;
private SurfaceView mSurfaceView;
private SurfaceControlViewHost mViewHost;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.accessibility_embedded_hierarchy_test_host_side);
mSurfaceView = findViewById(R.id.host_surfaceview);
mSurfaceView.getHolder().addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mViewHost = new SurfaceControlViewHost(this, this.getDisplay(),
mSurfaceView.getHostToken());
mSurfaceView.setChildSurfacePackage(mViewHost.getSurfacePackage());
View layout = getLayoutInflater().inflate(
R.layout.accessibility_embedded_hierarchy_test_embedded_side, null);
mViewHost.setView(layout, DEFAULT_WIDTH, DEFAULT_HEIGHT);
mCountDownLatch.countDown();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// No-op
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// No-op
}
public void waitForEmbeddedHierarchy() {
try {
assertTrue("timed out waiting for embedded hierarchy to init.",
mCountDownLatch.await(3, TimeUnit.SECONDS));
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
public void requestNewLayoutForTest(int x, int y) throws TimeoutException {
sUiAutomation.executeAndWaitForEvent(
() -> sInstrumentation.runOnMainSync(() -> {
mSurfaceView.setX(x);
mSurfaceView.setY(y);
mSurfaceView.requestLayout();
}),
(event) -> {
final Rect boundsInScreen = new Rect();
final AccessibilityWindowInfo window =
sUiAutomation.getRootInActiveWindow().getWindow();
window.getBoundsInScreen(boundsInScreen);
return !boundsInScreen.isEmpty();
}, DEFAULT_TIMEOUT_MS);
}
}
}