• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
20 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.homeScreenOrBust;
21 import static android.accessibilityservice.cts.utils.CtsTestUtils.DEFAULT_GLOBAL_TIMEOUT_MS;
22 import static android.accessibilityservice.cts.utils.CtsTestUtils.DEFAULT_IDLE_TIMEOUT_MS;
23 import static android.accessibilityservice.cts.utils.CtsTestUtils.isAutomotive;
24 
25 import static com.google.common.truth.Truth.assertThat;
26 
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assume.assumeFalse;
29 import static org.junit.Assume.assumeTrue;
30 
31 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
32 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
33 import android.accessibilityservice.AccessibilityService;
34 import android.accessibilityservice.AccessibilityService.MagnificationController;
35 import android.accessibilityservice.AccessibilityServiceInfo;
36 import android.accessibilityservice.MagnificationConfig;
37 import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
38 import android.accessibilityservice.cts.utils.SettingsSession;
39 import android.app.Instrumentation;
40 import android.app.UiAutomation;
41 import android.content.res.Resources;
42 import android.graphics.Rect;
43 import android.platform.test.annotations.AppModeFull;
44 import android.platform.test.annotations.Presubmit;
45 
46 import androidx.lifecycle.Lifecycle;
47 import androidx.test.core.app.ActivityScenario;
48 import androidx.test.ext.junit.runners.AndroidJUnit4;
49 import androidx.test.platform.app.InstrumentationRegistry;
50 
51 import com.android.compatibility.common.util.CddTest;
52 import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
53 import com.android.compatibility.common.util.TestUtils;
54 import com.android.compatibility.common.util.UserSettings;
55 
56 import org.junit.After;
57 import org.junit.AfterClass;
58 import org.junit.Before;
59 import org.junit.BeforeClass;
60 import org.junit.Rule;
61 import org.junit.Test;
62 import org.junit.rules.RuleChain;
63 import org.junit.runner.RunWith;
64 
65 import java.io.IOException;
66 import java.util.concurrent.atomic.AtomicBoolean;
67 
68 /**
69  * Class for testing {@See FullScreenMagnificationController}.
70  */
71 @AppModeFull
72 @RunWith(AndroidJUnit4.class)
73 @CddTest(requirements = {"3.10/C-1-1,C-1-2"})
74 @Presubmit
75 public class FullScreenMagnificationControllerTest {
76 
77     /** Maximum timeout while waiting for a config to be updated */
78     private static final int TIMEOUT_CONFIG_SECONDS = 15;
79 
80     private static final int BOUNDS_TOLERANCE = 1;
81 
82     private static final String DEVICE_CONFIG_NAMESPACE_WM = "window_manager";
83     private static final String DEVICE_CONFIG_KEY_ALWAYS_ON_MAGNIFIER =
84             "AlwaysOnMagnifier__enable_always_on_magnifier";
85     private static final String SETTING_KEY_MAGNIFICATION_ALWAYS_ON =
86             "accessibility_magnification_always_on_enabled";
87 
88     private static Instrumentation sInstrumentation;
89     private static UiAutomation sUiAutomation;
90     private StubMagnificationAccessibilityService mService;
91 
92     private ActivityScenario<AccessibilityWindowQueryActivity> mActivityScenario = null;
93 
94     private final InstrumentedAccessibilityServiceTestRule<StubMagnificationAccessibilityService>
95             mMagnificationAccessibilityServiceRule =
96                     new InstrumentedAccessibilityServiceTestRule<>(
97                             StubMagnificationAccessibilityService.class, false);
98 
99     // StateChangerRules starts UiAutomation without FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES.
100     // They have to be outer rule than other accessibility related rules.
101     private final DeviceConfigStateChangerRule mDeviceConfigStateChangerRule =
102             new DeviceConfigStateChangerRule(
103                     sInstrumentation.getContext(),
104                     DEVICE_CONFIG_NAMESPACE_WM,
105                     DEVICE_CONFIG_KEY_ALWAYS_ON_MAGNIFIER,
106                     "true");
107 
108     @Rule
109     public final RuleChain mRuleChain =
110             RuleChain.outerRule(mDeviceConfigStateChangerRule)
111                     .around(mMagnificationAccessibilityServiceRule)
112                     .around(new AccessibilityDumpOnFailureRule());
113 
114     @BeforeClass
oneTimeSetup()115     public static void oneTimeSetup() {
116         sInstrumentation = InstrumentationRegistry.getInstrumentation();
117         sUiAutomation = sInstrumentation.getUiAutomation();
118         AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
119         info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
120         sUiAutomation.setServiceInfo(info);
121     }
122 
123     @AfterClass
postTestTearDown()124     public static void postTestTearDown() {
125         sUiAutomation.destroy();
126     }
127 
128     @Before
setUp()129     public void setUp() throws Exception {
130         assumeFalse("Magnification is not supported on Automotive.",
131                 isAutomotive(sInstrumentation.getTargetContext()));
132         mService = mMagnificationAccessibilityServiceRule.enableService();
133 
134         // `setServiceInfo` resets magnification unless there's any magnification listener.
135         // In `homeScreenOrBust`, `uiAutomation.setServiceInfo` is done to ensure uiAutomation can
136         // listen the window events.
137         // Although we don't need to listen magnification here, adding an empty listener makes sure
138         // that magnification won't be reset by calling `setServiceInfo`.
139         // See also b/401998908.
140         final MagnificationController controller = mService.getMagnificationController();
141         controller.addListener(
142                 (controllerInner, region, scale1, centerX, centerY) -> {
143                     // Do nothing.
144                 });
145     }
146 
147     @After
cleanUp()148     public void cleanUp() {
149         if (mActivityScenario != null) {
150             mActivityScenario.close();
151         }
152     }
153 
154     @Test
testActivityTransitions_alwaysOnEnabled_keepMagnifiedDisabled_zoomOut()155     public void testActivityTransitions_alwaysOnEnabled_keepMagnifiedDisabled_zoomOut()
156             throws Exception {
157         assumeFalse(isKeepMagnifiedOnContextChangeEnabled());
158 
159         try (var session = getAlwaysOnSettingsSession(true)) {
160             mActivityScenario = launchActivityAndWait();
161 
162             zoomIn(/* scale= */ 2.0f);
163             // transition to home screen
164             homeScreenOrBust(sInstrumentation.getContext(), sUiAutomation);
165 
166             assertThat(currentScale()).isEqualTo(1f);
167             assertThat(isActivated()).isTrue();
168         }
169     }
170 
171     @Test
testActivityTransitions_alwaysOnEnabled_keepMagnifiedEnabled_keepZoom()172     public void testActivityTransitions_alwaysOnEnabled_keepMagnifiedEnabled_keepZoom()
173             throws Exception {
174         assumeTrue(isKeepMagnifiedOnContextChangeEnabled());
175 
176         try (var session = getAlwaysOnSettingsSession(true)) {
177             mActivityScenario = launchActivityAndWait();
178 
179             zoomIn(/* scale= */ 2.0f);
180             // transition to home screen
181             homeScreenOrBust(sInstrumentation.getContext(), sUiAutomation);
182 
183             assertThat(currentScale()).isEqualTo(2f);
184             assertThat(isActivated()).isTrue();
185         }
186     }
187 
188     @Test
testActivityTransitions_alwaysOnDisabled_disableMagnification()189     public void testActivityTransitions_alwaysOnDisabled_disableMagnification() throws Exception {
190         try (var session = getAlwaysOnSettingsSession(false)) {
191             mActivityScenario = launchActivityAndWait();
192 
193             zoomIn(/* scale= */ 2.0f);
194             // transition to home screen
195             homeScreenOrBust(sInstrumentation.getContext(), sUiAutomation);
196 
197             assertThat(currentScale()).isEqualTo(1f);
198             assertThat(isActivated()).isFalse();
199         }
200     }
201 
202     // launch an activity and waits for it to be on screen
launchActivityAndWait()203     private ActivityScenario<AccessibilityWindowQueryActivity> launchActivityAndWait()
204             throws Exception {
205         final var activityScenario =
206                 ActivityScenario.launch(AccessibilityWindowQueryActivity.class)
207                         .moveToState(Lifecycle.State.RESUMED);
208         sUiAutomation.waitForIdle(DEFAULT_IDLE_TIMEOUT_MS, DEFAULT_GLOBAL_TIMEOUT_MS);
209         return activityScenario;
210     }
211 
zoomIn(float scale)212     private void zoomIn(float scale) throws Exception {
213         final MagnificationController controller = mService.getMagnificationController();
214         final Rect rect = controller.getMagnificationRegion().getBounds();
215         final float x = rect.centerX();
216         final float y = rect.centerY();
217         final AtomicBoolean setConfig = new AtomicBoolean();
218 
219         final MagnificationConfig config = new MagnificationConfig.Builder()
220                 .setMode(MAGNIFICATION_MODE_FULLSCREEN)
221                 .setScale(scale)
222                 .setCenterX(x)
223                 .setCenterY(y).build();
224 
225         mService.runOnServiceSync(
226                 () -> {
227                     setConfig.set(controller.setMagnificationConfig(config, false));
228                 });
229         waitUntilMagnificationConfigEquals(controller, config);
230 
231         assertTrue("Failed to set config", setConfig.get());
232     }
233 
currentScale()234     private float currentScale() {
235         final MagnificationController controller = mService.getMagnificationController();
236         final MagnificationConfig config = controller.getMagnificationConfig();
237 
238         assertThat(config).isNotNull();
239 
240         return config.getScale();
241     }
242 
isActivated()243     private boolean isActivated() {
244         final MagnificationController controller = mService.getMagnificationController();
245         final MagnificationConfig config = controller.getMagnificationConfig();
246 
247         assertThat(config).isNotNull();
248 
249         return config.isActivated();
250     }
251 
waitUntilMagnificationConfigEquals( AccessibilityService.MagnificationController controller, MagnificationConfig config)252     private void waitUntilMagnificationConfigEquals(
253             AccessibilityService.MagnificationController controller,
254             MagnificationConfig config) throws Exception {
255         TestUtils.waitUntil(
256                 "Failed to apply the config. expected: " + config + " , actual: "
257                         + controller.getMagnificationConfig(), TIMEOUT_CONFIG_SECONDS,
258                 () -> {
259                     final MagnificationConfig actualConfig = controller.getMagnificationConfig();
260                     // If expected config activated is false, we just need to verify the activated
261                     // value is the same. Otherwise, we need to check all the actual values are
262                     // equal to the expected values.
263                     if (config.isActivated()) {
264                         return actualConfig.getMode() == config.getMode()
265                                 && actualConfig.isActivated() == config.isActivated()
266                                 && Float.compare(actualConfig.getScale(), config.getScale()) == 0
267                                 && (Math.abs(actualConfig.getCenterX() - config.getCenterX())
268                                 <= BOUNDS_TOLERANCE)
269                                 && (Math.abs(actualConfig.getCenterY() - config.getCenterY())
270                                 <= BOUNDS_TOLERANCE);
271                     } else {
272                         return actualConfig.isActivated() == config.isActivated();
273                     }
274                 });
275     }
276 
isKeepMagnifiedOnContextChangeEnabled()277     private boolean isKeepMagnifiedOnContextChangeEnabled() {
278         try {
279             return sInstrumentation.getTargetContext().getResources().getBoolean(
280                     Resources.getSystem().getIdentifier(
281                             "config_magnification_keep_zoom_level_when_context_changed", "bool",
282                             "android"));
283         } catch (Resources.NotFoundException ignore) {
284             return false;
285         }
286     }
287 
getAlwaysOnSettingsSession(boolean enabled)288     private static SettingsSession getAlwaysOnSettingsSession(boolean enabled) throws IOException {
289         return new SettingsSession(
290                 sInstrumentation,
291                 UserSettings.Namespace.SECURE,
292                 SETTING_KEY_MAGNIFICATION_ALWAYS_ON,
293                 enabled ? "1" : "0");
294     }
295 }
296