1 /*
2  * Copyright (C) 2016 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.accessibilityservice.cts;
18 
19 import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
20 
21 import static org.mockito.Mockito.any;
22 import static org.mockito.Mockito.anyFloat;
23 import static org.mockito.Mockito.eq;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.timeout;
26 import static org.mockito.Mockito.verify;
27 
28 import android.accessibility.cts.common.InstrumentedAccessibilityService;
29 import android.accessibility.cts.common.ShellCommandBuilder;
30 import android.accessibilityservice.AccessibilityService.MagnificationController;
31 import android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener;
32 import android.accessibilityservice.AccessibilityServiceInfo;
33 import android.app.Instrumentation;
34 import android.graphics.Rect;
35 import android.graphics.Region;
36 import android.platform.test.annotations.AppModeFull;
37 import android.test.InstrumentationTestCase;
38 
39 import java.util.concurrent.atomic.AtomicBoolean;
40 
41 /**
42  * Class for testing {@link AccessibilityServiceInfo}.
43  */
44 @AppModeFull
45 public class AccessibilityMagnificationTest extends InstrumentationTestCase {
46 
47     /** Maximum timeout when waiting for a magnification callback. */
48     public static final int LISTENER_TIMEOUT_MILLIS = 500;
49     public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
50             "accessibility_display_magnification_enabled";
51     private StubMagnificationAccessibilityService mService;
52     private Instrumentation mInstrumentation;
53 
54     @Override
setUp()55     public void setUp() throws Exception {
56         super.setUp();
57         ShellCommandBuilder.create(this.getInstrumentation())
58                 .deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
59                 .run();
60         mInstrumentation = getInstrumentation();
61         // Starting the service will force the accessibility subsystem to examine its settings, so
62         // it will update magnification in the process to disable it.
63         mService = StubMagnificationAccessibilityService.enableSelf(mInstrumentation);
64     }
65 
66     @Override
tearDown()67     protected void tearDown() throws Exception {
68         runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelfAndRemove));
69 
70         super.tearDown();
71     }
72 
testSetScale()73     public void testSetScale() {
74         final MagnificationController controller = mService.getMagnificationController();
75         final float scale = 2.0f;
76         final AtomicBoolean result = new AtomicBoolean();
77 
78         mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
79 
80         assertTrue("Failed to set scale", result.get());
81         assertEquals("Failed to apply scale", scale, controller.getScale());
82 
83         mService.runOnServiceSync(() -> result.set(controller.reset(false)));
84 
85         assertTrue("Failed to reset", result.get());
86         assertEquals("Failed to apply reset", 1.0f, controller.getScale());
87     }
88 
testSetScaleAndCenter()89     public void testSetScaleAndCenter() {
90         final MagnificationController controller = mService.getMagnificationController();
91         final Region region = controller.getMagnificationRegion();
92         final Rect bounds = region.getBounds();
93         final float scale = 2.0f;
94         final float x = bounds.left + (bounds.width() / 4.0f);
95         final float y = bounds.top + (bounds.height() / 4.0f);
96         final AtomicBoolean setScale = new AtomicBoolean();
97         final AtomicBoolean setCenter = new AtomicBoolean();
98         final AtomicBoolean result = new AtomicBoolean();
99 
100         mService.runOnServiceSync(() -> {
101             setScale.set(controller.setScale(scale, false));
102             setCenter.set(controller.setCenter(x, y, false));
103         });
104 
105         assertTrue("Failed to set scale", setScale.get());
106         assertEquals("Failed to apply scale", scale, controller.getScale());
107 
108         assertTrue("Failed to set center", setCenter.get());
109         assertEquals("Failed to apply center X", x, controller.getCenterX(), 5.0f);
110         assertEquals("Failed to apply center Y", y, controller.getCenterY(), 5.0f);
111 
112         mService.runOnServiceSync(() -> result.set(controller.reset(false)));
113 
114         assertTrue("Failed to reset", result.get());
115         assertEquals("Failed to apply reset", 1.0f, controller.getScale());
116     }
117 
testListener()118     public void testListener() {
119         final MagnificationController controller = mService.getMagnificationController();
120         final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
121         controller.addListener(listener);
122 
123         try {
124             final float scale = 2.0f;
125             final AtomicBoolean result = new AtomicBoolean();
126 
127             mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
128 
129             assertTrue("Failed to set scale", result.get());
130             verify(listener, timeout(LISTENER_TIMEOUT_MILLIS).atLeastOnce()).onMagnificationChanged(
131                     eq(controller), any(Region.class), eq(scale), anyFloat(), anyFloat());
132 
133             mService.runOnServiceSync(() -> result.set(controller.reset(false)));
134 
135             assertTrue("Failed to reset", result.get());
136             verify(listener, timeout(LISTENER_TIMEOUT_MILLIS).atLeastOnce()).onMagnificationChanged(
137                     eq(controller), any(Region.class), eq(1.0f), anyFloat(), anyFloat());
138         } finally {
139             controller.removeListener(listener);
140         }
141     }
142 
testMagnificationServiceShutsDownWhileMagnifying_shouldReturnTo1x()143     public void testMagnificationServiceShutsDownWhileMagnifying_shouldReturnTo1x() {
144         final MagnificationController controller = mService.getMagnificationController();
145         mService.runOnServiceSync(() -> controller.setScale(2.0f, false));
146 
147         mService.runOnServiceSync(() -> mService.disableSelf());
148         mService = null;
149         InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
150                 mInstrumentation, InstrumentedAccessibilityService.class);
151         final MagnificationController controller2 = service.getMagnificationController();
152         try {
153             assertEquals("Magnification must reset when a service dies",
154                     1.0f, controller2.getScale());
155         } finally {
156             service.runOnServiceSync(() -> service.disableSelf());
157         }
158     }
159 
testGetMagnificationRegion_whenCanControlMagnification_shouldNotBeEmpty()160     public void testGetMagnificationRegion_whenCanControlMagnification_shouldNotBeEmpty() {
161         final MagnificationController controller = mService.getMagnificationController();
162         Region magnificationRegion = controller.getMagnificationRegion();
163         assertFalse("Magnification region should not be empty when "
164                  + "magnification is being actively controlled", magnificationRegion.isEmpty());
165     }
166 
testGetMagnificationRegion_whenCantControlMagnification_shouldBeEmpty()167     public void testGetMagnificationRegion_whenCantControlMagnification_shouldBeEmpty() {
168         mService.runOnServiceSync(() -> mService.disableSelf());
169         mService = null;
170         InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
171                 mInstrumentation, InstrumentedAccessibilityService.class);
172         try {
173             final MagnificationController controller = service.getMagnificationController();
174             Region magnificationRegion = controller.getMagnificationRegion();
175             assertTrue("Magnification region should be empty when magnification "
176                     + "is not being actively controlled", magnificationRegion.isEmpty());
177         } finally {
178             service.runOnServiceSync(() -> service.disableSelf());
179         }
180     }
181 
testGetMagnificationRegion_whenMagnificationGesturesEnabled_shouldNotBeEmpty()182     public void testGetMagnificationRegion_whenMagnificationGesturesEnabled_shouldNotBeEmpty() {
183         ShellCommandBuilder.create(mInstrumentation)
184                 .putSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, "1")
185                 .run();
186         mService.runOnServiceSync(() -> mService.disableSelf());
187         mService = null;
188         InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
189                 mInstrumentation, InstrumentedAccessibilityService.class);
190         try {
191             final MagnificationController controller = service.getMagnificationController();
192             Region magnificationRegion = controller.getMagnificationRegion();
193             assertFalse("Magnification region should not be empty when magnification "
194                     + "gestures are active", magnificationRegion.isEmpty());
195         } finally {
196             service.runOnServiceSync(() -> service.disableSelf());
197             ShellCommandBuilder.create(mInstrumentation)
198                     .deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
199                     .run();
200         }
201     }
202 
testAnimatingMagnification()203     public void testAnimatingMagnification() throws InterruptedException {
204         final MagnificationController controller = mService.getMagnificationController();
205         final int timeBetweenAnimationChanges = 100;
206 
207         final float scale1 = 5.0f;
208         final float x1 = 500;
209         final float y1 = 1000;
210 
211         final float scale2 = 4.0f;
212         final float x2 = 500;
213         final float y2 = 1500;
214 
215         final float scale3 = 2.1f;
216         final float x3 = 700;
217         final float y3 = 700;
218 
219         for (int i = 0; i < 5; i++) {
220             mService.runOnServiceSync(() -> {
221                 controller.setScale(scale1, true);
222                 controller.setCenter(x1, y1, true);
223             });
224 
225             Thread.sleep(timeBetweenAnimationChanges);
226 
227             mService.runOnServiceSync(() -> {
228                 controller.setScale(scale2, true);
229                 controller.setCenter(x2, y2, true);
230             });
231 
232             Thread.sleep(timeBetweenAnimationChanges);
233 
234             mService.runOnServiceSync(() -> {
235                 controller.setScale(scale3, true);
236                 controller.setCenter(x3, y3, true);
237             });
238 
239             Thread.sleep(timeBetweenAnimationChanges);
240         }
241     }
242 }
243