blob: 1ef07244523d92316eedb1e73722a41f661e8b68 [file] [log] [blame]
/*
* Copyright (C) 2016 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.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyFloat;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibility.cts.common.InstrumentedAccessibilityService;
import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
import android.accessibility.cts.common.ShellCommandBuilder;
import android.accessibilityservice.AccessibilityService.MagnificationController;
import android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.MagnificationConfig;
import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.platform.test.annotations.AppModeFull;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.widget.Button;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.TestUtils;
import org.junit.AfterClass;
import org.junit.Assume;
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 org.mockito.ArgumentCaptor;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Class for testing {@link MagnificationController} and the magnification overlay window.
*/
@AppModeFull
@RunWith(AndroidJUnit4.class)
public class AccessibilityMagnificationTest {
/** Maximum timeout when waiting for a magnification callback. */
public static final int LISTENER_TIMEOUT_MILLIS = 500;
/** Maximum animation timeout when waiting for a magnification callback. */
public static final int LISTENER_ANIMATION_TIMEOUT_MILLIS = 1000;
public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
"accessibility_display_magnification_enabled";
private static UiAutomation sUiAutomation;
private StubMagnificationAccessibilityService mService;
private Instrumentation mInstrumentation;
private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
new AccessibilityDumpOnFailureRule();
private final ActivityTestRule<AccessibilityWindowQueryActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityWindowQueryActivity.class, false, false);
private InstrumentedAccessibilityServiceTestRule<InstrumentedAccessibilityService>
mInstrumentedAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
InstrumentedAccessibilityService.class, false);
private InstrumentedAccessibilityServiceTestRule<StubMagnificationAccessibilityService>
mMagnificationAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
StubMagnificationAccessibilityService.class, false);
@Rule
public final RuleChain mRuleChain = RuleChain
.outerRule(mActivityRule)
.around(mMagnificationAccessibilityServiceRule)
.around(mInstrumentedAccessibilityServiceRule)
.around(mDumpOnFailureRule);
@BeforeClass
public static void oneTimeSetUp() {
sUiAutomation = InstrumentationRegistry.getInstrumentation()
.getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
sUiAutomation.setServiceInfo(info);
}
@AfterClass
public static void postTestTearDown() {
sUiAutomation.destroy();
}
@Before
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
ShellCommandBuilder.create(sUiAutomation)
.deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
.run();
// Starting the service will force the accessibility subsystem to examine its settings, so
// it will update magnification in the process to disable it.
mService = mMagnificationAccessibilityServiceRule.enableService();
}
@Test
public void testSetScale() {
final MagnificationController controller = mService.getMagnificationController();
final float scale = 2.0f;
final AtomicBoolean result = new AtomicBoolean();
mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
assertTrue("Failed to set scale", result.get());
assertEquals("Failed to apply scale", scale, controller.getScale(), 0f);
mService.runOnServiceSync(() -> result.set(controller.reset(false)));
assertTrue("Failed to reset", result.get());
assertEquals("Failed to apply reset", 1.0f, controller.getScale(), 0f);
}
@Test
public void testSetScaleAndCenter() {
final MagnificationController controller = mService.getMagnificationController();
final Region region = controller.getMagnificationRegion();
final Rect bounds = region.getBounds();
final float scale = 2.0f;
final float x = bounds.left + (bounds.width() / 4.0f);
final float y = bounds.top + (bounds.height() / 4.0f);
final AtomicBoolean setScale = new AtomicBoolean();
final AtomicBoolean setCenter = new AtomicBoolean();
final AtomicBoolean result = new AtomicBoolean();
mService.runOnServiceSync(() -> {
setScale.set(controller.setScale(scale, false));
setCenter.set(controller.setCenter(x, y, false));
});
assertTrue("Failed to set scale", setScale.get());
assertEquals("Failed to apply scale", scale, controller.getScale(), 0f);
assertTrue("Failed to set center", setCenter.get());
assertEquals("Failed to apply center X", x, controller.getCenterX(), 5.0f);
assertEquals("Failed to apply center Y", y, controller.getCenterY(), 5.0f);
mService.runOnServiceSync(() -> result.set(controller.reset(false)));
assertTrue("Failed to reset", result.get());
assertEquals("Failed to apply reset", 1.0f, controller.getScale(), 0f);
}
@Test
public void testSetMagnificationConfig_expectedConfig() throws Exception {
final MagnificationController controller = mService.getMagnificationController();
final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
WindowManager.class);
final float scale = 2.0f;
final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
final AtomicBoolean setConfig = new AtomicBoolean();
final int targetMode = isWindowModeSupported(mInstrumentation.getContext())
? MAGNIFICATION_MODE_WINDOW : MAGNIFICATION_MODE_FULLSCREEN;
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(targetMode)
.setScale(scale)
.setCenterX(x)
.setCenterY(y).build();
mService.runOnServiceSync(() -> {
setConfig.set(controller.setMagnificationConfig(config, false));
});
waitUntilMagnificationConfig(controller, config);
assertTrue("Failed to set config", setConfig.get());
assertConfigEquals(config, controller.getMagnificationConfig());
final float newScale = scale + 1;
final Region region = controller.getMagnificationRegion();
final Rect bounds = region.getBounds();
final float newX = bounds.left + (bounds.width() / 4.0f);
final float newY = bounds.top + (bounds.height() / 4.0f);
final MagnificationConfig newConfig = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_FULLSCREEN).setScale(newScale).setCenterX(
newX).setCenterY(
newY).build();
mService.runOnServiceSync(() -> {
controller.setMagnificationConfig(newConfig, false);
});
waitUntilMagnificationConfig(controller, newConfig);
assertTrue("Failed to set config", setConfig.get());
assertConfigEquals(newConfig, controller.getMagnificationConfig());
}
@Test
public void testSetConfigWithDefaultModeAndCenter_expectedConfig() throws Exception {
final MagnificationController controller = mService.getMagnificationController();
final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
WindowManager.class);
final float scale = 3.0f;
final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
final AtomicBoolean setConfig = new AtomicBoolean();
final int targetMode = isWindowModeSupported(mInstrumentation.getContext())
? MAGNIFICATION_MODE_WINDOW : MAGNIFICATION_MODE_FULLSCREEN;
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(targetMode)
.setScale(scale)
.setCenterX(x)
.setCenterY(y)
.build();
mService.runOnServiceSync(
() -> setConfig.set(controller.setMagnificationConfig(config, false)));
waitUntilMagnificationConfig(controller, config);
assertTrue("Failed to set config", setConfig.get());
assertConfigEquals(config, controller.getMagnificationConfig());
final float newScale = scale + 1;
final MagnificationConfig newConfig = new MagnificationConfig.Builder()
.setScale(newScale).build();
final MagnificationConfig expectedConfig = obtainConfigBuilder(config).setScale(
newScale).build();
mService.runOnServiceSync(
() -> setConfig.set(controller.setMagnificationConfig(newConfig, false)));
waitUntilMagnificationConfig(controller, expectedConfig);
assertTrue("Failed to set config", setConfig.get());
assertConfigEquals(expectedConfig, controller.getMagnificationConfig());
}
@Test
public void testSetFullScreenConfigWithDefaultValues_windowModeEnabled_expectedConfig()
throws Exception {
final boolean windowModeSupported = isWindowModeSupported(mInstrumentation.getContext());
Assume.assumeTrue("window mode is not available", windowModeSupported);
final MagnificationController controller = mService.getMagnificationController();
final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
WindowManager.class);
final float scale = 3.0f;
final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
final AtomicBoolean setConfig = new AtomicBoolean();
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_WINDOW)
.setScale(scale)
.setCenterX(x)
.setCenterY(y).build();
mService.runOnServiceSync(
() -> setConfig.set(controller.setMagnificationConfig(config, false)));
waitUntilMagnificationConfig(controller, config);
assertTrue("Failed to set config", setConfig.get());
assertConfigEquals(config, controller.getMagnificationConfig());
final MagnificationConfig newConfig = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_FULLSCREEN)
.build();
mService.runOnServiceSync(
() -> setConfig.set(controller.setMagnificationConfig(newConfig, false)));
final MagnificationConfig expectedConfig = obtainConfigBuilder(config).setMode(
MAGNIFICATION_MODE_FULLSCREEN).build();
waitUntilMagnificationConfig(controller, expectedConfig);
assertTrue("Failed to set config", setConfig.get());
assertConfigEquals(expectedConfig, controller.getMagnificationConfig());
}
@Test
public void testSetMagnificationConfig_legacyApiExpectedResult() {
final MagnificationController controller = mService.getMagnificationController();
final Region region = controller.getMagnificationRegion();
final Rect bounds = region.getBounds();
final float scale = 2.0f;
final float x = bounds.left + (bounds.width() / 4.0f);
final float y = bounds.top + (bounds.height() / 4.0f);
final AtomicBoolean setConfig = new AtomicBoolean();
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_FULLSCREEN)
.setScale(scale)
.setCenterX(x)
.setCenterY(y).build();
try {
mService.runOnServiceSync(() -> {
setConfig.set(controller.setMagnificationConfig(config, false));
});
assertEquals("Failed to apply scale", scale, controller.getScale(), 0f);
assertEquals("Failed to apply center X", x, controller.getCenterX(), 5.0f);
assertEquals("Failed to apply center Y", y, controller.getCenterY(), 5.0f);
} finally {
mService.runOnServiceSync(() -> controller.resetCurrentMagnification(false));
}
}
@Test
public void testSetWindowModeConfig_connectionReset_expectedResult() throws Exception {
Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
final MagnificationController controller = mService.getMagnificationController();
final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
WindowManager.class);
final float scale = 2.0f;
final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_WINDOW)
.setScale(scale)
.setCenterX(x)
.setCenterY(y).build();
mService.runOnServiceSync(
() -> controller.setMagnificationConfig(config, /* animate= */ false));
waitUntilMagnificationConfig(controller, config);
// Test service is disabled and enabled to make the connection reset.
mService.runOnServiceSync(() -> mService.disableSelfAndRemove());
mService = null;
InstrumentedAccessibilityService service =
mMagnificationAccessibilityServiceRule.enableService();
MagnificationController controller2 = service.getMagnificationController();
try {
final float newScale = scale + 1;
final float newX = x + 10;
final float newY = y + 10;
final MagnificationConfig newConfig = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_WINDOW)
.setScale(newScale)
.setCenterX(newX)
.setCenterY(newY).build();
service.runOnServiceSync(
() -> controller2.setMagnificationConfig(newConfig, /* animate= */ false));
waitUntilMagnificationConfig(controller2, newConfig);
} finally {
service.runOnServiceSync(
() -> controller2.resetCurrentMagnification(false));
}
}
@Test
public void testSetWindowModeConfig_hasMagnificationOverlay() throws TimeoutException {
Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
final MagnificationController controller = mService.getMagnificationController();
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_WINDOW)
.setScale(2.0f)
.build();
try {
sUiAutomation.executeAndWaitForEvent(
() -> controller.setMagnificationConfig(config, false),
event -> sUiAutomation.getWindows().stream().anyMatch(
accessibilityWindowInfo -> accessibilityWindowInfo.getType()
== AccessibilityWindowInfo.TYPE_MAGNIFICATION_OVERLAY), 5000);
} finally {
controller.resetCurrentMagnification(false);
}
}
@Test
public void testServiceConnectionDisconnected_hasNoMagnificationOverlay()
throws TimeoutException {
Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
final MagnificationController controller = mService.getMagnificationController();
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_WINDOW)
.setScale(2.0f)
.build();
try {
sUiAutomation.executeAndWaitForEvent(
() -> controller.setMagnificationConfig(config, false),
event -> sUiAutomation.getWindows().stream().anyMatch(
accessibilityWindowInfo -> accessibilityWindowInfo.getType()
== AccessibilityWindowInfo.TYPE_MAGNIFICATION_OVERLAY), 5000);
sUiAutomation.executeAndWaitForEvent(
() -> mService.runOnServiceSync(() -> mService.disableSelfAndRemove()),
event -> sUiAutomation.getWindows().stream().noneMatch(
accessibilityWindowInfo -> accessibilityWindowInfo.getType()
== AccessibilityWindowInfo.TYPE_MAGNIFICATION_OVERLAY), 5000);
} finally {
controller.resetCurrentMagnification(false);
}
}
@Test
public void testGetMagnificationConfig_setConfigByLegacyApi_expectedResult() {
final MagnificationController controller = mService.getMagnificationController();
final Region region = controller.getMagnificationRegion();
final Rect bounds = region.getBounds();
final float scale = 2.0f;
final float x = bounds.left + (bounds.width() / 4.0f);
final float y = bounds.top + (bounds.height() / 4.0f);
mService.runOnServiceSync(() -> {
controller.setScale(scale, false);
controller.setCenter(x, y, false);
});
final MagnificationConfig config = controller.getMagnificationConfig();
assertEquals("Failed to apply scale", scale, config.getScale(), 0f);
assertEquals("Failed to apply center X", x, config.getCenterX(), 5.0f);
assertEquals("Failed to apply center Y", y, config.getCenterY(), 5.0f);
}
@Test
public void testListener() {
final MagnificationController controller = mService.getMagnificationController();
final OnMagnificationChangedListener spyListener = mock(
OnMagnificationChangedListener.class);
final OnMagnificationChangedListener listener =
(controller1, region, scale, centerX, centerY) ->
spyListener.onMagnificationChanged(controller1, region, scale, centerX,
centerY);
controller.addListener(listener);
try {
final float scale = 2.0f;
final AtomicBoolean result = new AtomicBoolean();
mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
assertTrue("Failed to set scale", result.get());
verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
eq(controller), any(Region.class), eq(scale), anyFloat(), anyFloat());
mService.runOnServiceSync(() -> result.set(controller.reset(false)));
assertTrue("Failed to reset", result.get());
verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
eq(controller), any(Region.class), eq(1.0f), anyFloat(), anyFloat());
} finally {
controller.removeListener(listener);
}
}
@Test
public void testListener_changeConfigByLegacyApi_notifyConfigChanged() {
final MagnificationController controller = mService.getMagnificationController();
final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
controller.addListener(listener);
try {
final float scale = 2.0f;
final AtomicBoolean result = new AtomicBoolean();
mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
assertTrue("Failed to set scale", result.get());
final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
MagnificationConfig.class);
verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
eq(controller), any(Region.class), configCaptor.capture());
assertEquals(scale, configCaptor.getValue().getScale(), 0);
reset(listener);
mService.runOnServiceSync(() -> result.set(controller.reset(false)));
assertTrue("Failed to reset", result.get());
verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
eq(controller), any(Region.class), configCaptor.capture());
assertEquals(1.0f, configCaptor.getValue().getScale(), 0);
} finally {
controller.removeListener(listener);
}
}
@Test
public void testListener_magnificationConfigChangedWithoutAnimation_notifyConfigChanged()
throws Exception {
final MagnificationController controller = mService.getMagnificationController();
final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
controller.addListener(listener);
final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
WindowManager.class);
final int targetMode = isWindowModeSupported(mInstrumentation.getContext())
? MAGNIFICATION_MODE_WINDOW : MAGNIFICATION_MODE_FULLSCREEN;
final float scale = 2.0f;
final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(targetMode)
.setScale(scale)
.setCenterX(x)
.setCenterY(y)
.build();
try {
mService.runOnServiceSync(() -> controller.setMagnificationConfig(config, false));
waitUntilMagnificationConfig(controller, config);
final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
MagnificationConfig.class);
verify(listener, timeout(LISTENER_TIMEOUT_MILLIS).atLeastOnce()).onMagnificationChanged(
eq(controller), any(Region.class), configCaptor.capture());
assertConfigEquals(config, configCaptor.getValue());
final float newScale = scale + 1;
final float newX = x + 10;
final float newY = y + 10;
final MagnificationConfig fullscreenConfig = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_FULLSCREEN)
.setScale(newScale)
.setCenterX(newX)
.setCenterY(newY).build();
reset(listener);
mService.runOnServiceSync(() -> {
controller.setMagnificationConfig(fullscreenConfig, false);
});
waitUntilMagnificationConfig(controller, fullscreenConfig);
verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
eq(controller), any(Region.class), configCaptor.capture());
assertConfigEquals(fullscreenConfig, configCaptor.getValue());
} finally {
mService.runOnServiceSync(() -> {
controller.resetCurrentMagnification(false);
controller.removeListener(listener);
});
}
}
@Test
public void testListener_magnificationConfigChangedWithAnimation_notifyConfigChanged() {
final MagnificationController controller = mService.getMagnificationController();
final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
controller.addListener(listener);
final int targetMode = isWindowModeSupported(mInstrumentation.getContext())
? MAGNIFICATION_MODE_WINDOW : MAGNIFICATION_MODE_FULLSCREEN;
final float scale = 2.0f;
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(targetMode)
.setScale(scale).build();
try {
mService.runOnServiceSync(
() -> controller.setMagnificationConfig(config, /* animate= */ true));
verify(listener, timeout(LISTENER_ANIMATION_TIMEOUT_MILLIS)).onMagnificationChanged(
eq(controller), any(Region.class), any(MagnificationConfig.class));
final float newScale = scale + 1;
final MagnificationConfig fullscreenConfig = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_FULLSCREEN)
.setScale(newScale).build();
reset(listener);
mService.runOnServiceSync(() -> {
controller.setMagnificationConfig(fullscreenConfig, /* animate= */ true);
});
verify(listener, timeout(LISTENER_ANIMATION_TIMEOUT_MILLIS)).onMagnificationChanged(
eq(controller), any(Region.class), any(MagnificationConfig.class));
} finally {
mService.runOnServiceSync(() -> {
controller.resetCurrentMagnification(false);
controller.removeListener(listener);
});
}
}
@Test
public void testMagnificationServiceShutsDownWhileMagnifying_fullscreen_shouldReturnTo1x() {
final MagnificationController controller = mService.getMagnificationController();
mService.runOnServiceSync(() -> controller.setScale(2.0f, false));
mService.runOnServiceSync(() -> mService.disableSelf());
mService = null;
InstrumentedAccessibilityService service =
mInstrumentedAccessibilityServiceRule.enableService();
final MagnificationController controller2 = service.getMagnificationController();
assertEquals("Magnification must reset when a service dies",
1.0f, controller2.getScale(), 0f);
}
@Test
public void testMagnificationServiceShutsDownWhileMagnifying_windowMode_shouldReturnTo1x()
throws Exception {
Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
final MagnificationController controller = mService.getMagnificationController();
final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
WindowManager.class);
final float scale = 2.0f;
final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_WINDOW)
.setScale(scale)
.setCenterX(x)
.setCenterY(y).build();
mService.runOnServiceSync(() -> {
controller.setMagnificationConfig(config, false);
});
waitUntilMagnificationConfig(controller, config);
mService.runOnServiceSync(() -> mService.disableSelf());
mService = null;
InstrumentedAccessibilityService service =
mInstrumentedAccessibilityServiceRule.enableService();
final MagnificationController controller2 = service.getMagnificationController();
assertEquals("Magnification must reset when a service dies",
1.0f, controller2.getMagnificationConfig().getScale(), 0f);
}
@Test
public void testGetMagnificationRegion_whenCanControlMagnification_shouldNotBeEmpty() {
final MagnificationController controller = mService.getMagnificationController();
Region magnificationRegion = controller.getMagnificationRegion();
assertFalse("Magnification region should not be empty when "
+ "magnification is being actively controlled", magnificationRegion.isEmpty());
}
@Test
public void testGetMagnificationRegion_whenCantControlMagnification_shouldBeEmpty() {
mService.runOnServiceSync(() -> mService.disableSelf());
mService = null;
InstrumentedAccessibilityService service =
mInstrumentedAccessibilityServiceRule.enableService();
final MagnificationController controller = service.getMagnificationController();
Region magnificationRegion = controller.getMagnificationRegion();
assertTrue("Magnification region should be empty when magnification "
+ "is not being actively controlled", magnificationRegion.isEmpty());
}
@Test
public void testGetMagnificationRegion_whenMagnificationGesturesEnabled_shouldNotBeEmpty() {
ShellCommandBuilder.create(sUiAutomation)
.putSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, "1")
.run();
mService.runOnServiceSync(() -> mService.disableSelf());
mService = null;
InstrumentedAccessibilityService service =
mInstrumentedAccessibilityServiceRule.enableService();
try {
final MagnificationController controller = service.getMagnificationController();
Region magnificationRegion = controller.getMagnificationRegion();
assertFalse("Magnification region should not be empty when magnification "
+ "gestures are active", magnificationRegion.isEmpty());
} finally {
ShellCommandBuilder.create(sUiAutomation)
.deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
.run();
}
}
@Test
public void testGetCurrentMagnificationRegion_fullscreen_exactRegionCenter() throws Exception {
final MagnificationController controller = mService.getMagnificationController();
final Region region = controller.getMagnificationRegion();
final Rect bounds = region.getBounds();
final float scale = 2.0f;
final float x = bounds.left + (bounds.width() / 4.0f);
final float y = bounds.top + (bounds.height() / 4.0f);
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_FULLSCREEN)
.setScale(scale)
.setCenterX(x)
.setCenterY(y).build();
try {
mService.runOnServiceSync(() -> {
controller.setMagnificationConfig(config, false);
});
waitUntilMagnificationConfig(controller, config);
final Region magnificationRegion = controller.getCurrentMagnificationRegion();
assertFalse(magnificationRegion.isEmpty());
} finally {
mService.runOnServiceSync(() -> {
controller.resetCurrentMagnification(false);
});
}
}
@Test
public void testGetCurrentMagnificationRegion_windowMode_exactRegionCenter() throws Exception {
Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
final MagnificationController controller = mService.getMagnificationController();
final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
WindowManager.class);
final float scale = 2.0f;
final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_WINDOW)
.setScale(scale)
.setCenterX(x)
.setCenterY(y).build();
try {
mService.runOnServiceSync(() -> {
controller.setMagnificationConfig(config, false);
});
waitUntilMagnificationConfig(controller, config);
final Region magnificationRegion = controller.getCurrentMagnificationRegion();
final Rect magnificationBounds = magnificationRegion.getBounds();
assertEquals(magnificationBounds.exactCenterX(), x, 0);
assertEquals(magnificationBounds.exactCenterY(), y, 0);
} finally {
mService.runOnServiceSync(() -> {
controller.resetCurrentMagnification(false);
});
}
}
@Test
public void testResetCurrentMagnificationRegion_WindowMode_regionIsEmpty() throws Exception {
Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
final MagnificationController controller = mService.getMagnificationController();
final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
WindowManager.class);
final float scale = 2.0f;
final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
final MagnificationConfig config = new MagnificationConfig.Builder()
.setMode(MAGNIFICATION_MODE_WINDOW)
.setScale(scale)
.setCenterX(x)
.setCenterY(y).build();
mService.runOnServiceSync(() -> {
controller.setMagnificationConfig(config, false);
});
waitUntilMagnificationConfig(controller, config);
assertEquals(scale, controller.getMagnificationConfig().getScale(), 0);
mService.runOnServiceSync(() -> {
controller.resetCurrentMagnification(false);
});
assertEquals(1.0f, controller.getMagnificationConfig().getScale(), 0);
assertTrue(controller.getCurrentMagnificationRegion().isEmpty());
}
@Test
public void testAnimatingMagnification() throws InterruptedException {
final MagnificationController controller = mService.getMagnificationController();
final int timeBetweenAnimationChanges = 100;
final float scale1 = 5.0f;
final float x1 = 500;
final float y1 = 1000;
final float scale2 = 4.0f;
final float x2 = 500;
final float y2 = 1500;
final float scale3 = 2.1f;
final float x3 = 700;
final float y3 = 700;
for (int i = 0; i < 5; i++) {
mService.runOnServiceSync(() -> {
controller.setScale(scale1, true);
controller.setCenter(x1, y1, true);
});
Thread.sleep(timeBetweenAnimationChanges);
mService.runOnServiceSync(() -> {
controller.setScale(scale2, true);
controller.setCenter(x2, y2, true);
});
Thread.sleep(timeBetweenAnimationChanges);
mService.runOnServiceSync(() -> {
controller.setScale(scale3, true);
controller.setCenter(x3, y3, true);
});
Thread.sleep(timeBetweenAnimationChanges);
}
}
@Test
public void testA11yNodeInfoVisibility_whenOutOfMagnifiedArea_shouldVisible()
throws Exception{
final Activity activity = launchActivityAndWaitForItToBeOnscreen(
mInstrumentation, sUiAutomation, mActivityRule);
final MagnificationController controller = mService.getMagnificationController();
final Rect magnifyBounds = controller.getMagnificationRegion().getBounds();
final float scale = 8.0f;
final Button button = activity.findViewById(R.id.button1);
adjustViewBoundsIfNeeded(button, scale, magnifyBounds);
final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow()
.findAccessibilityNodeInfosByViewId(
"android.accessibilityservice.cts:id/button1").get(0);
assertNotNull("Can't find button on the screen", buttonNode);
assertTrue("Button should be visible", buttonNode.isVisibleToUser());
// Get right-bottom center position
final float centerX = magnifyBounds.left + (((float) magnifyBounds.width() / (2.0f * scale))
* ((2.0f * scale) - 1.0f));
final float centerY = magnifyBounds.top + (((float) magnifyBounds.height() / (2.0f * scale))
* ((2.0f * scale) - 1.0f));
try {
waitOnMagnificationChanged(controller, scale, centerX, centerY);
// Waiting for UI refresh
mInstrumentation.waitForIdleSync();
buttonNode.refresh();
final Rect boundsInScreen = new Rect();
final DisplayMetrics displayMetrics = new DisplayMetrics();
buttonNode.getBoundsInScreen(boundsInScreen);
activity.getDisplay().getMetrics(displayMetrics);
final Rect displayRect = new Rect(0, 0,
displayMetrics.widthPixels, displayMetrics.heightPixels);
// The boundsInScreen of button is adjusted to outside of screen by framework,
// for example, Rect(-xxx, -xxx, -xxx, -xxx). Intersection of button and screen
// should be empty.
assertFalse("Button shouldn't be on the screen, screen is " + displayRect
+ ", button bounds is " + boundsInScreen,
Rect.intersects(displayRect, boundsInScreen));
assertTrue("Button should be visible", buttonNode.isVisibleToUser());
} finally {
mService.runOnServiceSync(() -> controller.reset(false));
}
}
private void waitOnMagnificationChanged(MagnificationController controller, float newScale,
float newCenterX, float newCenterY) {
final OnMagnificationChangedListener spyListener = mock(
OnMagnificationChangedListener.class);
final OnMagnificationChangedListener listener =
(controller1, region, scale, centerX, centerY) ->
spyListener.onMagnificationChanged(controller1, region, scale, centerX,
centerY);
controller.addListener(listener);
try {
final AtomicBoolean setScale = new AtomicBoolean();
final AtomicBoolean setCenter = new AtomicBoolean();
mService.runOnServiceSync(() -> {
setScale.set(controller.setScale(newScale, false));
});
assertTrue("Failed to set scale", setScale.get());
verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
eq(controller), any(Region.class), eq(newScale), anyFloat(), anyFloat());
reset(spyListener);
mService.runOnServiceSync(() -> {
setCenter.set(controller.setCenter(newCenterX, newCenterY, false));
});
assertTrue("Failed to set center", setCenter.get());
verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
eq(controller), any(Region.class), anyFloat(), eq(newCenterX), eq(newCenterY));
} finally {
controller.removeListener(listener);
}
}
/**
* Adjust top-left view bounds if it's still in the magnified viewport after sets magnification
* scale and move centers to bottom-right.
*/
private void adjustViewBoundsIfNeeded(View topLeftview, float scale, Rect magnifyBounds) {
final Point magnifyViewportTopLeft = new Point();
magnifyViewportTopLeft.x = (int)((scale - 1.0f) * ((float) magnifyBounds.width() / scale));
magnifyViewportTopLeft.y = (int)((scale - 1.0f) * ((float) magnifyBounds.height() / scale));
magnifyViewportTopLeft.offset(magnifyBounds.left, magnifyBounds.top);
final int[] viewLocation = new int[2];
topLeftview.getLocationOnScreen(viewLocation);
final Rect viewBounds = new Rect(viewLocation[0], viewLocation[1],
viewLocation[0] + topLeftview.getWidth(),
viewLocation[1] + topLeftview.getHeight());
if (viewBounds.right < magnifyViewportTopLeft.x
&& viewBounds.bottom < magnifyViewportTopLeft.y) {
// no need
return;
}
final ViewGroup.LayoutParams layoutParams = topLeftview.getLayoutParams();
if (viewBounds.right >= magnifyViewportTopLeft.x) {
layoutParams.width = topLeftview.getWidth() - 1
- (viewBounds.right - magnifyViewportTopLeft.x);
assertTrue("Needs to fix layout", layoutParams.width > 0);
}
if (viewBounds.bottom >= magnifyViewportTopLeft.y) {
layoutParams.height = topLeftview.getHeight() - 1
- (viewBounds.bottom - magnifyViewportTopLeft.y);
assertTrue("Needs to fix layout", layoutParams.height > 0);
}
mInstrumentation.runOnMainSync(() -> topLeftview.setLayoutParams(layoutParams));
// Waiting for UI refresh
mInstrumentation.waitForIdleSync();
}
private void waitUntilMagnificationConfig(MagnificationController controller,
MagnificationConfig config) throws Exception {
TestUtils.waitUntil(
"Failed to apply the config. expected: " + config + " , actual: "
+ controller.getMagnificationConfig(), 5,
() -> {
final MagnificationConfig actualConfig = controller.getMagnificationConfig();
return actualConfig.getMode() == config.getMode()
&& Float.compare(actualConfig.getScale(), config.getScale()) == 0
&& Float.compare(actualConfig.getCenterX(), config.getCenterX()) == 0
&& Float.compare(actualConfig.getCenterY(), config.getCenterY()) == 0;
});
}
private void assertConfigEquals(MagnificationConfig expected, MagnificationConfig result) {
assertEquals("Failed to apply mode", expected.getMode(),
result.getMode(), 0f);
assertEquals("Failed to apply scale", expected.getScale(),
result.getScale(), 0f);
assertEquals("Failed to apply center X", expected.getCenterX(),
result.getCenterX(), 5.0f);
assertEquals("Failed to apply center Y", expected.getCenterY(),
result.getCenterY(), 5.0f);
}
private static boolean isWindowModeSupported(Context context) {
return context.getPackageManager().hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION);
}
private static MagnificationConfig.Builder obtainConfigBuilder(MagnificationConfig config) {
MagnificationConfig.Builder builder = new MagnificationConfig.Builder();
builder.setMode(config.getMode())
.setScale(config.getScale())
.setCenterX(config.getCenterX())
.setCenterY(config.getCenterY());
return builder;
}
}