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.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
20 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
21 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
22 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
23 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
24 import static android.accessibilityservice.cts.utils.CtsTestUtils.isAutomotive;
25 import static android.content.pm.PackageManager.FEATURE_WINDOW_MAGNIFICATION;
26 import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED;
27 
28 import static org.junit.Assert.assertEquals;
29 import static org.junit.Assert.assertFalse;
30 import static org.junit.Assert.assertNotNull;
31 import static org.junit.Assert.assertTrue;
32 import static org.junit.Assume.assumeFalse;
33 import static org.mockito.Mockito.any;
34 import static org.mockito.Mockito.anyFloat;
35 import static org.mockito.Mockito.eq;
36 import static org.mockito.Mockito.mock;
37 import static org.mockito.Mockito.reset;
38 import static org.mockito.Mockito.timeout;
39 import static org.mockito.Mockito.verify;
40 
41 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
42 import android.accessibility.cts.common.InstrumentedAccessibilityService;
43 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
44 import android.accessibility.cts.common.ShellCommandBuilder;
45 import android.accessibilityservice.AccessibilityService.MagnificationController;
46 import android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener;
47 import android.accessibilityservice.AccessibilityServiceInfo;
48 import android.accessibilityservice.MagnificationConfig;
49 import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
50 import android.app.Activity;
51 import android.app.Instrumentation;
52 import android.app.UiAutomation;
53 import android.content.Context;
54 import android.content.pm.PackageManager;
55 import android.graphics.PixelFormat;
56 import android.graphics.Point;
57 import android.graphics.Rect;
58 import android.graphics.Region;
59 import android.os.SystemClock;
60 import android.platform.test.annotations.AppModeFull;
61 import android.platform.test.annotations.Presubmit;
62 import android.platform.test.annotations.RequiresFlagsDisabled;
63 import android.platform.test.annotations.RequiresFlagsEnabled;
64 import android.platform.test.flag.junit.CheckFlagsRule;
65 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
66 import android.util.DisplayMetrics;
67 import android.util.Log;
68 import android.view.Gravity;
69 import android.view.View;
70 import android.view.ViewGroup;
71 import android.view.WindowManager;
72 import android.view.accessibility.AccessibilityNodeInfo;
73 import android.view.accessibility.AccessibilityWindowInfo;
74 import android.widget.Button;
75 
76 import androidx.test.filters.FlakyTest;
77 import androidx.test.platform.app.InstrumentationRegistry;
78 import androidx.test.rule.ActivityTestRule;
79 import androidx.test.runner.AndroidJUnit4;
80 
81 import com.android.compatibility.common.util.CddTest;
82 import com.android.compatibility.common.util.SystemUtil;
83 import com.android.compatibility.common.util.TestUtils;
84 import com.android.window.flags.Flags;
85 
86 import org.junit.After;
87 import org.junit.AfterClass;
88 import org.junit.Assume;
89 import org.junit.Before;
90 import org.junit.BeforeClass;
91 import org.junit.Rule;
92 import org.junit.Test;
93 import org.junit.rules.RuleChain;
94 import org.junit.runner.RunWith;
95 import org.mockito.ArgumentCaptor;
96 
97 import java.util.concurrent.TimeoutException;
98 import java.util.concurrent.atomic.AtomicBoolean;
99 import java.util.concurrent.atomic.AtomicReference;
100 import java.util.function.Consumer;
101 
102 /**
103  * Class for testing {@link MagnificationController} and the magnification overlay window.
104  */
105 @AppModeFull
106 @RunWith(AndroidJUnit4.class)
107 @CddTest(requirements = {"3.10/C-1-1,C-1-2"})
108 @Presubmit
109 public class AccessibilityMagnificationTest {
110 
111     /** Maximum timeout when waiting for a magnification callback. */
112     public static final int LISTENER_TIMEOUT_MILLIS = 500;
113     /** Maximum animation timeout when waiting for a magnification callback. */
114     public static final int LISTENER_ANIMATION_TIMEOUT_MILLIS = 1000;
115     public static final int BOUNDS_TOLERANCE = 1;
116     public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
117             "accessibility_display_magnification_enabled";
118 
119     /** Maximum timeout while waiting for a config to be updated */
120     public static final int TIMEOUT_CONFIG_SECONDS = 15;
121 
122     // From WindowManager.java.
123     public static final int TYPE_NAVIGATION_BAR_PANEL = 2024;
124 
125     private static UiAutomation sUiAutomation;
126 
127     private static final String TAG = "AccessibilityMagnificationTest";
128 
129     private StubMagnificationAccessibilityService mService;
130     private Instrumentation mInstrumentation;
131 
132     private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
133             new AccessibilityDumpOnFailureRule();
134 
135     private final ActivityTestRule<AccessibilityWindowQueryActivity> mActivityRule =
136             new ActivityTestRule<>(AccessibilityWindowQueryActivity.class, false, false);
137 
138     private InstrumentedAccessibilityServiceTestRule<InstrumentedAccessibilityService>
139             mInstrumentedAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
140                     InstrumentedAccessibilityService.class, false);
141 
142     private InstrumentedAccessibilityServiceTestRule<StubMagnificationAccessibilityService>
143             mMagnificationAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
144                     StubMagnificationAccessibilityService.class, false);
145 
146     private final CheckFlagsRule mCheckFlagsRule =
147             DeviceFlagsValueProvider.createCheckFlagsRule(sUiAutomation);
148 
149     @Rule
150     public final RuleChain mRuleChain = RuleChain
151             .outerRule(mActivityRule)
152             .around(mMagnificationAccessibilityServiceRule)
153             .around(mInstrumentedAccessibilityServiceRule)
154             .around(mDumpOnFailureRule)
155             .around(mCheckFlagsRule);
156 
157     @BeforeClass
oneTimeSetUp()158     public static void oneTimeSetUp() {
159         sUiAutomation = InstrumentationRegistry.getInstrumentation()
160                 .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
161         final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
162         info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
163         sUiAutomation.setServiceInfo(info);
164     }
165 
166     @AfterClass
postTestTearDown()167     public static void postTestTearDown() {
168         sUiAutomation.destroy();
169     }
170 
171     @Before
setUp()172     public void setUp() throws Exception {
173         mInstrumentation = InstrumentationRegistry.getInstrumentation();
174         assumeFalse("Magnification is not supported on Automotive.",
175                 isAutomotive(mInstrumentation.getTargetContext()));
176         ShellCommandBuilder.create(sUiAutomation)
177                 .deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
178                 .run();
179         // Starting the service will force the accessibility subsystem to examine its settings, so
180         // it will update magnification in the process to disable it.
181         mService = mMagnificationAccessibilityServiceRule.enableService();
182     }
183 
184     @After
tearDown()185     public void tearDown() {
186         // Ensure the magnification is deactivated after each test case. For some test cases that
187         // would disable mService during the test, they still need to reset magnification themselves
188         // after the test.
189         if (mService != null) {
190             mService.runOnServiceSync(() -> {
191                 mService.getMagnificationController().resetCurrentMagnification(/* animate= */
192                         false);
193             });
194         }
195 
196         // Since window magnification may need times to remove the magnification window, we would
197         // like to wait and ensure the overlays for magnification are removed here.
198         if (isMagnificationOverlayExisting() || doesAccessibilityMagnificationOverlayExist()) {
199             // Do nothing, we just want to wait for the event and check the
200             // overlays for magnification are removed
201             try {
202                 sUiAutomation.executeAndWaitForEvent(() -> {},
203                         event -> !(isMagnificationOverlayExisting()
204                                 || doesAccessibilityMagnificationOverlayExist()),
205                         5000);
206             } catch (TimeoutException timeoutException) {
207                 // Double check the overlay is not exists in case there is no event sent
208                 assertTrue(!(isMagnificationOverlayExisting()
209                                || doesAccessibilityMagnificationOverlayExist()));
210             }
211         }
212     }
213 
214     @Test
testSetScale()215     public void testSetScale() {
216         final MagnificationController controller = mService.getMagnificationController();
217         final float scale = 2.0f;
218         final AtomicBoolean result = new AtomicBoolean();
219 
220         mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
221 
222         assertTrue(getSetterErrorMessage("Failed to set scale"), result.get());
223         assertEquals("Failed to apply scale", scale, controller.getScale(), 0f);
224 
225         mService.runOnServiceSync(() -> result.set(controller.reset(false)));
226 
227         assertTrue("Failed to reset", result.get());
228         assertEquals("Failed to apply reset", 1.0f, controller.getScale(), 0f);
229     }
230 
231     @Test
testSetScaleAndCenter()232     public void testSetScaleAndCenter() {
233         final MagnificationController controller = mService.getMagnificationController();
234         final Region region = controller.getMagnificationRegion();
235         final Rect bounds = region.getBounds();
236         final float scale = 2.0f;
237         final float x = bounds.left + (bounds.width() / 4.0f);
238         final float y = bounds.top + (bounds.height() / 4.0f);
239         final AtomicBoolean setScale = new AtomicBoolean();
240         final AtomicBoolean setCenter = new AtomicBoolean();
241         final AtomicBoolean result = new AtomicBoolean();
242 
243         mService.runOnServiceSync(() -> {
244             setScale.set(controller.setScale(scale, false));
245             setCenter.set(controller.setCenter(x, y, false));
246         });
247 
248         assertTrue(getSetterErrorMessage("Failed to set scale"), setScale.get());
249         assertEquals("Failed to apply scale", scale, controller.getScale(), 0f);
250 
251         assertTrue(getSetterErrorMessage("Failed to set center"), setCenter.get());
252         assertEquals("Failed to apply center X", x, controller.getCenterX(), 5.0f);
253         assertEquals("Failed to apply center Y", y, controller.getCenterY(), 5.0f);
254 
255         mService.runOnServiceSync(() -> result.set(controller.reset(false)));
256 
257         assertTrue("Failed to reset", result.get());
258         assertEquals("Failed to apply reset", 1.0f, controller.getScale(), 0f);
259     }
260 
261     @Test
testSetMagnificationConfig_expectedConfig()262     public void testSetMagnificationConfig_expectedConfig() throws Exception {
263         final MagnificationController controller = mService.getMagnificationController();
264         final Rect rect = controller.getMagnificationRegion().getBounds();
265         final float scale = 2.0f;
266         final float x = rect.centerX();
267         final float y = rect.centerY();
268         final AtomicBoolean setConfig = new AtomicBoolean();
269 
270         final int targetMode = isWindowModeSupported(mInstrumentation.getContext())
271                 ? MAGNIFICATION_MODE_WINDOW : MAGNIFICATION_MODE_FULLSCREEN;
272         final MagnificationConfig config = new MagnificationConfig.Builder()
273                 .setMode(targetMode)
274                 .setScale(scale)
275                 .setCenterX(x)
276                 .setCenterY(y).build();
277 
278         mService.runOnServiceSync(() -> {
279             setConfig.set(controller.setMagnificationConfig(config, false));
280         });
281         assertTrue(getSetterErrorMessage("Failed to set config"), setConfig.get());
282 
283         waitUntilMagnificationConfig(controller, config);
284         assertConfigEquals(config, controller.getMagnificationConfig());
285 
286         final float newScale = scale + 1;
287         final Region region = controller.getMagnificationRegion();
288         final Rect bounds = region.getBounds();
289         final float newX = bounds.left + (bounds.width() / 4.0f);
290         final float newY = bounds.top + (bounds.height() / 4.0f);
291         final MagnificationConfig newConfig = new MagnificationConfig.Builder()
292                 .setMode(MAGNIFICATION_MODE_FULLSCREEN).setScale(newScale).setCenterX(
293                         newX).setCenterY(
294                         newY).build();
295         mService.runOnServiceSync(() -> {
296             controller.setMagnificationConfig(newConfig, false);
297         });
298         assertTrue(getSetterErrorMessage("Failed to set config"), setConfig.get());
299 
300         waitUntilMagnificationConfig(controller, newConfig);
301         assertConfigEquals(newConfig, controller.getMagnificationConfig());
302     }
303 
304     @Test
testSetConfigWithDefaultModeAndCenter_expectedConfig()305     public void testSetConfigWithDefaultModeAndCenter_expectedConfig() throws Exception {
306         final MagnificationController controller = mService.getMagnificationController();
307         final Rect bounds = controller.getMagnificationRegion().getBounds();
308         final float scale = 3.0f;
309         final float x = bounds.centerX();
310         final float y = bounds.centerY();
311         final AtomicBoolean setConfig = new AtomicBoolean();
312 
313         final int targetMode = isWindowModeSupported(mInstrumentation.getContext())
314                 ? MAGNIFICATION_MODE_WINDOW : MAGNIFICATION_MODE_FULLSCREEN;
315         final MagnificationConfig config = new MagnificationConfig.Builder()
316                 .setMode(targetMode)
317                 .setScale(scale)
318                 .setCenterX(x)
319                 .setCenterY(y)
320                 .build();
321 
322         mService.runOnServiceSync(
323                 () -> setConfig.set(controller.setMagnificationConfig(config, false)));
324         assertTrue(getSetterErrorMessage("Failed to set config"), setConfig.get());
325 
326         waitUntilMagnificationConfig(controller, config);
327         assertConfigEquals(config, controller.getMagnificationConfig());
328 
329         final float newScale = scale + 1;
330         final MagnificationConfig newConfig = new MagnificationConfig.Builder()
331                 .setScale(newScale).build();
332         final MagnificationConfig expectedConfig = obtainConfigBuilder(config).setScale(
333                 newScale).build();
334 
335         mService.runOnServiceSync(
336                 () -> setConfig.set(controller.setMagnificationConfig(newConfig, false)));
337         assertTrue(getSetterErrorMessage("Failed to set config"), setConfig.get());
338 
339         waitUntilMagnificationConfig(controller, expectedConfig);
340         assertConfigEquals(expectedConfig, controller.getMagnificationConfig());
341 
342         mService.runOnServiceSync(
343                 () -> controller.resetCurrentMagnification(/* animate= */ false));
344     }
345 
346     @Test
testSetConfigWithActivatedFalse_expectedConfig()347     public void testSetConfigWithActivatedFalse_expectedConfig() throws Exception {
348         final MagnificationController controller = mService.getMagnificationController();
349         final Rect bounds = controller.getMagnificationRegion().getBounds();
350         final float scale = 3.0f;
351         final float x = bounds.centerX();
352         final float y = bounds.centerY();
353         final AtomicBoolean setConfig = new AtomicBoolean();
354 
355         final int targetMode = isWindowModeSupported(mInstrumentation.getContext())
356                 ? MAGNIFICATION_MODE_WINDOW : MAGNIFICATION_MODE_FULLSCREEN;
357         final MagnificationConfig config = new MagnificationConfig.Builder()
358                 .setMode(targetMode)
359                 .setScale(scale)
360                 .setCenterX(x)
361                 .setCenterY(y)
362                 .build();
363 
364         mService.runOnServiceSync(
365                 () -> setConfig.set(controller.setMagnificationConfig(config,
366                         /* animate= */ false)));
367         assertTrue(getSetterErrorMessage("Failed to set config"), setConfig.get());
368 
369         waitUntilMagnificationConfig(controller, config);
370         assertConfigEquals(config, controller.getMagnificationConfig());
371 
372         final MagnificationConfig newConfig = new MagnificationConfig.Builder()
373                 .setActivated(false).build();
374         final MagnificationConfig expectedConfig = obtainConfigBuilder(config).setActivated(
375                 false).build();
376 
377         mService.runOnServiceSync(
378                 () -> setConfig.set(controller.setMagnificationConfig(newConfig,
379                         /* animate= */ false)));
380         assertTrue(getSetterErrorMessage("Failed to set config"), setConfig.get());
381 
382         waitUntilMagnificationConfig(controller, expectedConfig);
383         assertConfigEquals(expectedConfig, controller.getMagnificationConfig());
384     }
385 
386     @Test
testSetConfigWithActivatedFalse_magnificationDisabled_expectedReturnedValue()387     public void testSetConfigWithActivatedFalse_magnificationDisabled_expectedReturnedValue()
388             throws Exception {
389         final MagnificationController controller = mService.getMagnificationController();
390         final AtomicBoolean setConfig = new AtomicBoolean();
391 
392         final MagnificationConfig config = new MagnificationConfig.Builder()
393                 .setActivated(false)
394                 .build();
395 
396         mService.runOnServiceSync(
397                 () -> setConfig.set(controller.setMagnificationConfig(config,
398                         /* animate= */ false)));
399         assertFalse(getSetterErrorMessage("Failed to set config"), setConfig.get());
400 
401         waitUntilMagnificationConfig(controller, config);
402     }
403 
404     @Test
testSetFullScreenConfigWithDefaultValues_windowModeEnabled_expectedConfig()405     public void testSetFullScreenConfigWithDefaultValues_windowModeEnabled_expectedConfig()
406             throws Exception {
407         final boolean windowModeSupported = isWindowModeSupported(mInstrumentation.getContext());
408         Assume.assumeTrue("window mode is not available", windowModeSupported);
409 
410         final MagnificationController controller = mService.getMagnificationController();
411         final Rect bounds = controller.getMagnificationRegion().getBounds();
412         final float scale = 3.0f;
413         final float x = bounds.centerX();
414         final float y = bounds.centerY();
415         final AtomicBoolean setConfig = new AtomicBoolean();
416 
417         final MagnificationConfig config = new MagnificationConfig.Builder()
418                 .setMode(MAGNIFICATION_MODE_WINDOW)
419                 .setScale(scale)
420                 .setCenterX(x)
421                 .setCenterY(y).build();
422 
423         mService.runOnServiceSync(
424                 () -> setConfig.set(controller.setMagnificationConfig(config, false)));
425         assertTrue(getSetterErrorMessage("Failed to set config"), setConfig.get());
426 
427         waitUntilMagnificationConfig(controller, config);
428         assertConfigEquals(config, controller.getMagnificationConfig());
429 
430         final MagnificationConfig newConfig = new MagnificationConfig.Builder()
431                 .setMode(MAGNIFICATION_MODE_FULLSCREEN)
432                 .build();
433 
434         mService.runOnServiceSync(
435                 () -> setConfig.set(controller.setMagnificationConfig(newConfig, false)));
436         assertTrue("Set config should have failed but didn't", setConfig.get());
437 
438         final MagnificationConfig expectedConfig = obtainConfigBuilder(config).setMode(
439                 MAGNIFICATION_MODE_FULLSCREEN).build();
440 
441         waitUntilMagnificationConfig(controller, expectedConfig);
442         assertConfigEquals(expectedConfig, controller.getMagnificationConfig());
443     }
444 
445     @Test
testSetFullScreenConfigWithScaleOne_expectedConfig()446     public void testSetFullScreenConfigWithScaleOne_expectedConfig() {
447         final MagnificationController controller = mService.getMagnificationController();
448         final float scale = 1.0f;
449         final AtomicBoolean setConfig = new AtomicBoolean();
450 
451         final MagnificationConfig config = new MagnificationConfig.Builder()
452                 .setMode(MAGNIFICATION_MODE_FULLSCREEN)
453                 .setScale(scale)
454                 .build();
455 
456         mService.runOnServiceSync(
457                 () -> setConfig.set(controller.setMagnificationConfig(config,
458                         /* animate= */ false)));
459         assertTrue(getSetterErrorMessage("Failed to set config"), setConfig.get());
460         assertTrue(controller.getMagnificationConfig().isActivated());
461         assertEquals(1.0f, controller.getMagnificationConfig().getScale(), 0);
462     }
463 
464     @Test
testResetCurrentMagnification_fullScreenEnabled_expectedConfig()465     public void testResetCurrentMagnification_fullScreenEnabled_expectedConfig()
466             throws Exception {
467         final MagnificationController controller = mService.getMagnificationController();
468         final Rect bounds = controller.getMagnificationRegion().getBounds();
469         final float scale = 3.0f;
470         final float x = bounds.centerX();
471         final float y = bounds.centerY();
472         final AtomicBoolean setConfig = new AtomicBoolean();
473 
474         final MagnificationConfig config = new MagnificationConfig.Builder()
475                 .setMode(MAGNIFICATION_MODE_FULLSCREEN)
476                 .setScale(scale)
477                 .setCenterX(x)
478                 .setCenterY(y)
479                 .build();
480 
481         mService.runOnServiceSync(
482                 () -> setConfig.set(controller.setMagnificationConfig(config,
483                         /* animate= */ false)));
484         assertTrue(getSetterErrorMessage("Failed to set config"), setConfig.get());
485 
486         waitUntilMagnificationConfig(controller, config);
487         assertConfigEquals(config, controller.getMagnificationConfig());
488 
489         mService.runOnServiceSync(() -> {
490             controller.resetCurrentMagnification(/* animate= */ false);
491         });
492 
493         assertFalse(controller.getMagnificationConfig().isActivated());
494         assertEquals(1.0f, controller.getMagnificationConfig().getScale(), 0);
495     }
496 
497     @Test
testSetMagnificationConfig_legacyApiExpectedResult()498     public void testSetMagnificationConfig_legacyApiExpectedResult() {
499         final MagnificationController controller = mService.getMagnificationController();
500         final Region region = controller.getMagnificationRegion();
501         final Rect bounds = region.getBounds();
502         final float scale = 2.0f;
503         final float x = bounds.left + (bounds.width() / 4.0f);
504         final float y = bounds.top + (bounds.height() / 4.0f);
505         final AtomicBoolean setConfig = new AtomicBoolean();
506         final MagnificationConfig config = new MagnificationConfig.Builder()
507                 .setMode(MAGNIFICATION_MODE_FULLSCREEN)
508                 .setScale(scale)
509                 .setCenterX(x)
510                 .setCenterY(y).build();
511         try {
512             mService.runOnServiceSync(() -> {
513                 setConfig.set(controller.setMagnificationConfig(config, false));
514             });
515             assertTrue(getSetterErrorMessage("Failed to set config"), setConfig.get());
516 
517             assertEquals("Failed to apply scale", scale, controller.getScale(), 0f);
518             assertEquals("Failed to apply center X", x, controller.getCenterX(), 5.0f);
519             assertEquals("Failed to apply center Y", y, controller.getCenterY(), 5.0f);
520         } finally {
521             mService.runOnServiceSync(() -> controller.resetCurrentMagnification(false));
522         }
523     }
524 
525     @Test
testSetWindowModeConfig_connectionReset_expectedResult()526     public void testSetWindowModeConfig_connectionReset_expectedResult() throws Exception {
527         Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
528 
529         final MagnificationController controller = mService.getMagnificationController();
530         final Rect bounds = controller.getMagnificationRegion().getBounds();
531         final float scale = 2.0f;
532         final float x = bounds.centerX();
533         final float y = bounds.centerY();
534 
535         final MagnificationConfig config = new MagnificationConfig.Builder()
536                 .setMode(MAGNIFICATION_MODE_WINDOW)
537                 .setScale(scale)
538                 .setCenterX(x)
539                 .setCenterY(y).build();
540 
541         mService.runOnServiceSync(
542                 () -> controller.setMagnificationConfig(config, /* animate= */ false));
543 
544         waitUntilMagnificationConfig(controller, config);
545 
546         // Test service is disabled and enabled to make the connection reset.
547         mService.runOnServiceSync(() -> mService.disableSelfAndRemove());
548         mService = null;
549         InstrumentedAccessibilityService service =
550                 mMagnificationAccessibilityServiceRule.enableService();
551         MagnificationController controller2 = service.getMagnificationController();
552         try {
553             final float newScale = scale + 1;
554             final float newX = x + bounds.width() / 4.0f;
555             final float newY = y + bounds.height() / 4.0f;
556             final MagnificationConfig newConfig = new MagnificationConfig.Builder()
557                     .setMode(MAGNIFICATION_MODE_WINDOW)
558                     .setScale(newScale)
559                     .setCenterX(newX)
560                     .setCenterY(newY).build();
561 
562             service.runOnServiceSync(
563                     () -> controller2.setMagnificationConfig(newConfig, /* animate= */ false));
564 
565             waitUntilMagnificationConfig(controller2, newConfig);
566         } finally {
567             service.runOnServiceSync(
568                     () -> controller2.resetCurrentMagnification(false));
569         }
570     }
571 
572     @Test
573     @RequiresFlagsDisabled(com.android.systemui.Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
testSetWindowModeConfig_hasMagnificationOverlay()574     public void testSetWindowModeConfig_hasMagnificationOverlay() throws TimeoutException {
575         Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
576 
577         final MagnificationController controller = mService.getMagnificationController();
578         final MagnificationConfig config = new MagnificationConfig.Builder()
579                 .setMode(MAGNIFICATION_MODE_WINDOW)
580                 .setScale(2.0f)
581                 .build();
582 
583         try {
584             sUiAutomation.executeAndWaitForEvent(
585                     () -> controller.setMagnificationConfig(config, false),
586                     event -> isMagnificationOverlayExisting(), 5000);
587         } finally {
588             mService.runOnServiceSync(() -> controller.resetCurrentMagnification(false));
589         }
590     }
591 
592     @Test
593     @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
testSetWindowModeConfig_hasAccessibilityOverlay()594     public void testSetWindowModeConfig_hasAccessibilityOverlay() throws TimeoutException {
595         Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
596 
597         final MagnificationController controller = mService.getMagnificationController();
598         final MagnificationConfig config = new MagnificationConfig.Builder()
599                 .setMode(MAGNIFICATION_MODE_WINDOW)
600                 .setScale(2.0f)
601                 .build();
602 
603         try {
604             sUiAutomation.executeAndWaitForEvent(
605                     () -> controller.setMagnificationConfig(config, false),
606                     event -> doesAccessibilityMagnificationOverlayExist(), 5000);
607         } finally {
608             mService.runOnServiceSync(() -> controller.resetCurrentMagnification(false));
609         }
610     }
611 
612     @Test
613     @RequiresFlagsDisabled(com.android.systemui.Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
testServiceConnectionDisconnected_hasNoMagnificationOverlay()614     public void testServiceConnectionDisconnected_hasNoMagnificationOverlay()
615             throws TimeoutException {
616         Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
617 
618         final MagnificationController controller = mService.getMagnificationController();
619         final MagnificationConfig config = new MagnificationConfig.Builder()
620                 .setMode(MAGNIFICATION_MODE_WINDOW)
621                 .setScale(2.0f)
622                 .build();
623 
624         try {
625             sUiAutomation.executeAndWaitForEvent(
626                     () -> controller.setMagnificationConfig(config, false),
627                     event -> isMagnificationOverlayExisting(), 5000);
628 
629             sUiAutomation.executeAndWaitForEvent(
630                     () -> mService.runOnServiceSync(() -> mService.disableSelfAndRemove()),
631                     event -> !isMagnificationOverlayExisting(), 5000);
632         } finally {
633             mService.runOnServiceSync(() -> controller.resetCurrentMagnification(false));
634         }
635     }
636 
637     @Test
638     @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_CREATE_WINDOWLESS_WINDOW_MAGNIFIER)
testServiceConnectionDisconnected_hasNoAccessibilityOverlay()639     public void testServiceConnectionDisconnected_hasNoAccessibilityOverlay()
640             throws TimeoutException {
641         Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
642 
643         final MagnificationController controller = mService.getMagnificationController();
644         final MagnificationConfig config = new MagnificationConfig.Builder()
645                 .setMode(MAGNIFICATION_MODE_WINDOW)
646                 .setScale(2.0f)
647                 .build();
648 
649         try {
650             sUiAutomation.executeAndWaitForEvent(
651                     () -> controller.setMagnificationConfig(config, false),
652                     event -> doesAccessibilityMagnificationOverlayExist(), 5000);
653 
654             sUiAutomation.executeAndWaitForEvent(
655                     () -> mService.runOnServiceSync(() -> mService.disableSelfAndRemove()),
656                     event -> !doesAccessibilityMagnificationOverlayExist(), 5000);
657         } finally {
658             mService.runOnServiceSync(() -> controller.resetCurrentMagnification(false));
659         }
660     }
661 
662     @Test
testGetMagnificationConfig_setConfigByLegacyApi_expectedResult()663     public void testGetMagnificationConfig_setConfigByLegacyApi_expectedResult() {
664         final MagnificationController controller = mService.getMagnificationController();
665         final Region region = controller.getMagnificationRegion();
666         final Rect bounds = region.getBounds();
667         final float scale = 2.0f;
668         final float x = bounds.left + (bounds.width() / 4.0f);
669         final float y = bounds.top + (bounds.height() / 4.0f);
670         mService.runOnServiceSync(() -> {
671             controller.setScale(scale, false);
672             controller.setCenter(x, y, false);
673         });
674 
675         final MagnificationConfig config = controller.getMagnificationConfig();
676 
677         assertTrue(config.isActivated());
678         assertEquals("Failed to apply scale", scale, config.getScale(), 0f);
679         assertEquals("Failed to apply center X", x, config.getCenterX(), 5.0f);
680         assertEquals("Failed to apply center Y", y, config.getCenterY(), 5.0f);
681     }
682 
683     @Test
testGetMagnificationConfig_setConfigByLegacyApiAndReset_expectedResult()684     public void testGetMagnificationConfig_setConfigByLegacyApiAndReset_expectedResult() {
685         final MagnificationController controller = mService.getMagnificationController();
686         final Region region = controller.getMagnificationRegion();
687         final Rect bounds = region.getBounds();
688         final float scale = 2.0f;
689         final float x = bounds.left + (bounds.width() / 4.0f);
690         final float y = bounds.top + (bounds.height() / 4.0f);
691         mService.runOnServiceSync(() -> {
692             controller.setScale(scale, /* animate= */ false);
693             controller.setCenter(x, y, /* animate= */ false);
694             controller.reset(/* animate= */ false);
695         });
696 
697         final MagnificationConfig config = controller.getMagnificationConfig();
698 
699         assertFalse(config.isActivated());
700     }
701 
702     @Test
testListener()703     public void testListener() {
704         final MagnificationController controller = mService.getMagnificationController();
705         final OnMagnificationChangedListener spyListener = mock(
706                 OnMagnificationChangedListener.class);
707         final OnMagnificationChangedListener listener =
708                 (controller1, region, scale, centerX, centerY) ->
709                         spyListener.onMagnificationChanged(controller1, region, scale, centerX,
710                                 centerY);
711         controller.addListener(listener);
712 
713         try {
714             final float scale = 2.0f;
715             final AtomicBoolean result = new AtomicBoolean();
716 
717             mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
718 
719             assertTrue(getSetterErrorMessage("Failed to set scale"), result.get());
720             verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
721                     eq(controller), any(Region.class), eq(scale), anyFloat(), anyFloat());
722 
723             mService.runOnServiceSync(() -> result.set(controller.reset(false)));
724 
725             assertTrue("Failed to reset", result.get());
726             verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
727                     eq(controller), any(Region.class), eq(1.0f), anyFloat(), anyFloat());
728         } finally {
729             controller.removeListener(listener);
730         }
731     }
732 
733     @Test
testListener_changeConfigByLegacyApi_notifyConfigChanged()734     public void testListener_changeConfigByLegacyApi_notifyConfigChanged() {
735         final MagnificationController controller = mService.getMagnificationController();
736         final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
737         controller.addListener(listener);
738 
739         try {
740             final float scale = 2.0f;
741             final AtomicBoolean result = new AtomicBoolean();
742 
743             mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
744 
745             assertTrue(getSetterErrorMessage("Failed to set scale"), result.get());
746             final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
747                     MagnificationConfig.class);
748             verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
749                     eq(controller), any(Region.class), configCaptor.capture());
750             assertTrue(configCaptor.getValue().isActivated());
751             assertEquals(scale, configCaptor.getValue().getScale(), 0);
752 
753             reset(listener);
754             mService.runOnServiceSync(() -> result.set(controller.reset(false)));
755 
756             assertTrue("Failed to reset", result.get());
757             verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
758                     eq(controller), any(Region.class), configCaptor.capture());
759             assertFalse(configCaptor.getValue().isActivated());
760             assertEquals(1.0f, configCaptor.getValue().getScale(), 0);
761         } finally {
762             controller.removeListener(listener);
763         }
764     }
765 
766     @Test
testListener_magnificationConfigChangedWithoutAnimation_notifyConfigChanged()767     public void testListener_magnificationConfigChangedWithoutAnimation_notifyConfigChanged()
768             throws Exception {
769         final MagnificationController controller = mService.getMagnificationController();
770         final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
771         controller.addListener(listener);
772         final int targetMode = isWindowModeSupported(mInstrumentation.getContext())
773                 ? MAGNIFICATION_MODE_WINDOW : MAGNIFICATION_MODE_FULLSCREEN;
774         final Rect bounds = controller.getMagnificationRegion().getBounds();
775         final float scale = 2.0f;
776         final float x = bounds.centerX();
777         final float y = bounds.centerY();
778         final MagnificationConfig config = new MagnificationConfig.Builder()
779                 .setMode(targetMode)
780                 .setScale(scale)
781                 .setCenterX(x)
782                 .setCenterY(y)
783                 .build();
784 
785         try {
786             mService.runOnServiceSync(() -> controller.setMagnificationConfig(config, false));
787             waitUntilMagnificationConfig(controller, config);
788 
789             final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
790                     MagnificationConfig.class);
791             verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
792                     eq(controller), any(Region.class), configCaptor.capture());
793             assertConfigEquals(config, configCaptor.getValue());
794 
795             final float newScale = scale + 1;
796             final float newX = x + bounds.width() / 4.0f;
797             final float newY = y + bounds.height() / 4.0f;
798             final MagnificationConfig fullscreenConfig = new MagnificationConfig.Builder()
799                     .setMode(MAGNIFICATION_MODE_FULLSCREEN)
800                     .setScale(newScale)
801                     .setCenterX(newX)
802                     .setCenterY(newY).build();
803 
804             reset(listener);
805             mService.runOnServiceSync(() -> {
806                 controller.setMagnificationConfig(fullscreenConfig, false);
807             });
808             waitUntilMagnificationConfig(controller, fullscreenConfig);
809 
810             verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
811                     eq(controller), any(Region.class), configCaptor.capture());
812             assertConfigEquals(fullscreenConfig, configCaptor.getValue());
813         } finally {
814             mService.runOnServiceSync(() -> {
815                 controller.resetCurrentMagnification(false);
816                 controller.removeListener(listener);
817             });
818         }
819     }
820 
821     @Test
testListener_magnificationConfigChangedWithAnimation_notifyConfigChanged()822     public void testListener_magnificationConfigChangedWithAnimation_notifyConfigChanged() {
823         final MagnificationController controller = mService.getMagnificationController();
824         final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
825         controller.addListener(listener);
826         final int targetMode = isWindowModeSupported(mInstrumentation.getContext())
827                 ? MAGNIFICATION_MODE_WINDOW : MAGNIFICATION_MODE_FULLSCREEN;
828         final float scale = 2.0f;
829         final MagnificationConfig config = new MagnificationConfig.Builder()
830                 .setMode(targetMode)
831                 .setScale(scale).build();
832 
833         try {
834             mService.runOnServiceSync(
835                     () -> controller.setMagnificationConfig(config, /* animate= */ true));
836 
837             verify(listener, timeout(LISTENER_ANIMATION_TIMEOUT_MILLIS)).onMagnificationChanged(
838                     eq(controller), any(Region.class), any(MagnificationConfig.class));
839 
840             final float newScale = scale + 1;
841             final MagnificationConfig fullscreenConfig = new MagnificationConfig.Builder()
842                     .setMode(MAGNIFICATION_MODE_FULLSCREEN)
843                     .setScale(newScale).build();
844 
845             reset(listener);
846             mService.runOnServiceSync(() -> {
847                 controller.setMagnificationConfig(fullscreenConfig, /* animate= */ true);
848             });
849 
850             verify(listener, timeout(LISTENER_ANIMATION_TIMEOUT_MILLIS)).onMagnificationChanged(
851                     eq(controller), any(Region.class), any(MagnificationConfig.class));
852         } finally {
853             mService.runOnServiceSync(() -> {
854                 controller.resetCurrentMagnification(false);
855                 controller.removeListener(listener);
856             });
857         }
858     }
859 
860     @Test
testListener_transitionFromFullScreenToWindow_notifyConfigChanged()861     public void testListener_transitionFromFullScreenToWindow_notifyConfigChanged()
862             throws Exception {
863         Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
864 
865         final MagnificationController controller = mService.getMagnificationController();
866         final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
867         final Rect bounds = controller.getMagnificationRegion().getBounds();
868         final float scale = 2.0f;
869         final float x = bounds.centerX();
870         final float y = bounds.centerY();
871         final MagnificationConfig windowConfig = new MagnificationConfig.Builder()
872                 .setMode(MAGNIFICATION_MODE_WINDOW)
873                 .setScale(scale)
874                 .setCenterX(x)
875                 .setCenterY(y)
876                 .build();
877         final float newScale = scale + 1;
878         final float newX = x + bounds.width() / 4.0f;
879         final float newY = y + bounds.height() / 4.0f;
880         final MagnificationConfig fullscreenConfig = new MagnificationConfig.Builder()
881                 .setMode(MAGNIFICATION_MODE_FULLSCREEN)
882                 .setScale(newScale)
883                 .setCenterX(newX)
884                 .setCenterY(newY).build();
885 
886         try {
887             final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
888                     MagnificationConfig.class);
889 
890             mService.runOnServiceSync(() -> {
891                 controller.setMagnificationConfig(fullscreenConfig, false);
892             });
893             waitUntilMagnificationConfig(controller, fullscreenConfig);
894 
895             controller.addListener(listener);
896             mService.runOnServiceSync(() -> controller.setMagnificationConfig(windowConfig, false));
897             waitUntilMagnificationConfig(controller, windowConfig);
898 
899             verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
900                     eq(controller), any(Region.class), configCaptor.capture());
901             assertConfigEquals(windowConfig, configCaptor.getValue());
902         } finally {
903             mService.runOnServiceSync(() -> {
904                 controller.resetCurrentMagnification(false);
905                 controller.removeListener(listener);
906             });
907         }
908     }
909 
910     @Test
testListener_resetCurrentMagnification_notifyConfigChanged()911     public void testListener_resetCurrentMagnification_notifyConfigChanged() throws Exception {
912         final MagnificationController controller = mService.getMagnificationController();
913         final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
914         final int targetMode = isWindowModeSupported(mInstrumentation.getContext())
915                 ? MAGNIFICATION_MODE_WINDOW : MAGNIFICATION_MODE_FULLSCREEN;
916         final Rect bounds = controller.getMagnificationRegion().getBounds();
917         final float scale = 2.0f;
918         final float x = bounds.centerX();
919         final float y = bounds.centerY();
920         final MagnificationConfig config = new MagnificationConfig.Builder()
921                 .setMode(targetMode)
922                 .setScale(scale)
923                 .setCenterX(x)
924                 .setCenterY(y)
925                 .build();
926 
927         try {
928             mService.runOnServiceSync(
929                     () -> controller.setMagnificationConfig(config, /* animate= */ false));
930             waitUntilMagnificationConfig(controller, config);
931 
932             controller.addListener(listener);
933             controller.resetCurrentMagnification(false);
934 
935             final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
936                     MagnificationConfig.class);
937             verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
938                     eq(controller), any(Region.class), configCaptor.capture());
939             assertFalse(configCaptor.getValue().isActivated());
940             assertEquals(1.0f, configCaptor.getValue().getScale(), 0);
941         } finally {
942             controller.removeListener(listener);
943         }
944     }
945 
946     @Test
testMagnificationServiceShutsDownWhileMagnifying_fullscreen_shouldReturnTo1x()947     public void testMagnificationServiceShutsDownWhileMagnifying_fullscreen_shouldReturnTo1x() {
948         final MagnificationController controller = mService.getMagnificationController();
949         mService.runOnServiceSync(() -> controller.setScale(2.0f, false));
950 
951         mService.runOnServiceSync(() -> mService.disableSelf());
952         mService = null;
953         InstrumentedAccessibilityService service =
954                 mInstrumentedAccessibilityServiceRule.enableService();
955         final MagnificationController controller2 = service.getMagnificationController();
956         assertEquals("Magnification must reset when a service dies",
957                 1.0f, controller2.getScale(), 0f);
958         assertFalse(controller2.getMagnificationConfig().isActivated());
959     }
960 
961     @Test
testMagnificationServiceShutsDownWhileMagnifying_windowMode_shouldReturnTo1x()962     public void testMagnificationServiceShutsDownWhileMagnifying_windowMode_shouldReturnTo1x()
963             throws Exception {
964         Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
965 
966         final MagnificationController controller = mService.getMagnificationController();
967         final Rect bounds = controller.getMagnificationRegion().getBounds();
968         final float scale = 2.0f;
969         final float x = bounds.centerX();
970         final float y = bounds.centerY();
971 
972         final MagnificationConfig config = new MagnificationConfig.Builder()
973                 .setMode(MAGNIFICATION_MODE_WINDOW)
974                 .setScale(scale)
975                 .setCenterX(x)
976                 .setCenterY(y).build();
977 
978         mService.runOnServiceSync(() -> {
979             controller.setMagnificationConfig(config, false);
980         });
981         waitUntilMagnificationConfig(controller, config);
982 
983         mService.runOnServiceSync(() -> mService.disableSelf());
984         mService = null;
985         InstrumentedAccessibilityService service =
986                 mInstrumentedAccessibilityServiceRule.enableService();
987         final MagnificationController controller2 = service.getMagnificationController();
988         assertEquals("Magnification must reset when a service dies",
989                 1.0f, controller2.getMagnificationConfig().getScale(), 0f);
990         assertFalse(controller2.getMagnificationConfig().isActivated());
991     }
992 
993     @Test
testGetMagnificationRegion_whenCanControlMagnification_shouldNotBeEmpty()994     public void testGetMagnificationRegion_whenCanControlMagnification_shouldNotBeEmpty() {
995         final MagnificationController controller = mService.getMagnificationController();
996         Region magnificationRegion = controller.getMagnificationRegion();
997         assertFalse("Magnification region should not be empty when "
998                  + "magnification is being actively controlled", magnificationRegion.isEmpty());
999     }
1000 
1001     @Test
testGetMagnificationRegion_whenCantControlMagnification_shouldBeEmpty()1002     public void testGetMagnificationRegion_whenCantControlMagnification_shouldBeEmpty() {
1003         mService.runOnServiceSync(() -> mService.disableSelf());
1004         mService = null;
1005         InstrumentedAccessibilityService service =
1006                 mInstrumentedAccessibilityServiceRule.enableService();
1007         final MagnificationController controller = service.getMagnificationController();
1008         Region magnificationRegion = controller.getMagnificationRegion();
1009         assertTrue("Magnification region should be empty when magnification "
1010                 + "is not being actively controlled", magnificationRegion.isEmpty());
1011     }
1012 
1013     @Test
testGetMagnificationRegion_whenMagnificationGesturesEnabled_shouldNotBeEmpty()1014     public void testGetMagnificationRegion_whenMagnificationGesturesEnabled_shouldNotBeEmpty() {
1015         ShellCommandBuilder.create(sUiAutomation)
1016                 .putSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, "1")
1017                 .run();
1018         mService.runOnServiceSync(() -> mService.disableSelf());
1019         mService = null;
1020         InstrumentedAccessibilityService service =
1021                 mInstrumentedAccessibilityServiceRule.enableService();
1022         try {
1023             final MagnificationController controller = service.getMagnificationController();
1024             Region magnificationRegion = controller.getMagnificationRegion();
1025             assertFalse("Magnification region should not be empty when magnification "
1026                     + "gestures are active", magnificationRegion.isEmpty());
1027         } finally {
1028             ShellCommandBuilder.create(sUiAutomation)
1029                     .deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
1030                     .run();
1031         }
1032     }
1033 
1034     @Test
testGetCurrentMagnificationRegion_fullscreen_exactRegionCenter()1035     public void testGetCurrentMagnificationRegion_fullscreen_exactRegionCenter() throws Exception {
1036         final MagnificationController controller = mService.getMagnificationController();
1037         final Region region = controller.getMagnificationRegion();
1038         final Rect bounds = region.getBounds();
1039         final float scale = 2.0f;
1040         final float x = bounds.left + (bounds.width() / 4.0f);
1041         final float y = bounds.top + (bounds.height() / 4.0f);
1042 
1043         final MagnificationConfig config = new MagnificationConfig.Builder()
1044                 .setMode(MAGNIFICATION_MODE_FULLSCREEN)
1045                 .setScale(scale)
1046                 .setCenterX(x)
1047                 .setCenterY(y).build();
1048         try {
1049             mService.runOnServiceSync(() -> {
1050                 controller.setMagnificationConfig(config, false);
1051             });
1052             waitUntilMagnificationConfig(controller, config);
1053 
1054             final Region magnificationRegion = controller.getCurrentMagnificationRegion();
1055             assertFalse(magnificationRegion.isEmpty());
1056         } finally {
1057             mService.runOnServiceSync(() -> {
1058                 controller.resetCurrentMagnification(false);
1059             });
1060         }
1061     }
1062 
1063     @Test
testMagnificationRegion_hasNonMagnifiableWindow_excludeWindowExactTouchableRegion()1064     public void testMagnificationRegion_hasNonMagnifiableWindow_excludeWindowExactTouchableRegion()
1065             throws Exception {
1066         final MagnificationController controller = mService.getMagnificationController();
1067         final Region expectedMagnificationRegion = controller.getMagnificationRegion();
1068         final Rect magnifyBounds = controller.getMagnificationRegion().getBounds();
1069 
1070         mService.runOnServiceSync(() -> controller.setScale(4.0f, /* animate =*/ false));
1071 
1072         Button createdButton = null;
1073         try {
1074             // We should ensure all the button area is in magnifyBounds for the following tests
1075             Button button = addNonMagnifiableWindow(R.string.button1, "button1", params -> {
1076                 params.gravity = Gravity.LEFT | Gravity.TOP;
1077                 params.x = magnifyBounds.left;
1078                 params.y = magnifyBounds.top;
1079                 params.width = magnifyBounds.width();
1080                 params.height = magnifyBounds.height() / 8;
1081             });
1082             createdButton = button;
1083 
1084             // Set two small rects at the top-left and right-bottom corner as the touchable region
1085             Rect buttonBounds = new Rect();
1086             button.getBoundsOnScreen(buttonBounds, false);
1087             int touchableRegionWidth = buttonBounds.width() / 4;
1088             int touchableRegionHeight = buttonBounds.height() / 4;
1089             Rect touchableRect1 = new Rect(0, 0, touchableRegionWidth, touchableRegionHeight);
1090             Rect touchableRect2 = new Rect(
1091                     buttonBounds.width() - touchableRegionWidth,
1092                     buttonBounds.height() - touchableRegionHeight,
1093                     buttonBounds.width(),
1094                     buttonBounds.height());
1095             Region touchableRegion = new Region();
1096             touchableRegion.op(touchableRect1, Region.Op.UNION);
1097             touchableRegion.op(touchableRect2, Region.Op.UNION);
1098             mInstrumentation.runOnMainSync(() ->
1099                     button.getRootSurfaceControl().setTouchableRegion(touchableRegion));
1100 
1101             // Offset the touchable rects to screen coordinates
1102             touchableRect1.offset(buttonBounds.left, buttonBounds.top);
1103             touchableRect2.offset(buttonBounds.left, buttonBounds.top);
1104             TestUtils.waitUntil(
1105                     "The updated magnification region is not expected. expected: "
1106                             + expectedMagnificationRegion + " , actual: "
1107                             + controller.getMagnificationRegion() + ", touchableRect1: "
1108                             + touchableRect1 + ", touchableRect2: " + touchableRect2,
1109                     TIMEOUT_CONFIG_SECONDS,
1110                     () -> {
1111                         final Region magnificationRegion = controller.getMagnificationRegion();
1112 
1113                         Region tmpRegion = new Region();
1114 
1115                         // magnificationRegion should not intersect with real touchable rects
1116                         tmpRegion.set(magnificationRegion);
1117                         tmpRegion.op(touchableRect1, Region.Op.INTERSECT);
1118                         if (!tmpRegion.isEmpty()) {
1119                             return false;
1120                         }
1121                         tmpRegion.set(magnificationRegion);
1122                         tmpRegion.op(touchableRect2, Region.Op.INTERSECT);
1123                         if (!tmpRegion.isEmpty()) {
1124                             return false;
1125                         }
1126 
1127                         // The region united by magnificationRegion and touchable rects
1128                         // should equal to the original region
1129                         tmpRegion.set(magnificationRegion);
1130                         tmpRegion.union(touchableRect1);
1131                         tmpRegion.union(touchableRect2);
1132                         return expectedMagnificationRegion.equals(tmpRegion);
1133                     });
1134         } finally {
1135             if (createdButton != null) {
1136                 mInstrumentation.getContext().getSystemService(WindowManager.class).removeView(
1137                         createdButton);
1138             }
1139             // After the non-magnifiable window is removed, the magnificationRegion should restore
1140             // to the original region.
1141             waitUntilMagnificationRegion(controller, expectedMagnificationRegion);
1142 
1143             mService.runOnServiceSync(() -> controller.reset(/* animate =*/ false));
1144         }
1145     }
1146 
1147     @Test
testGetCurrentMagnificationRegion_windowMode_exactRegionCenter()1148     public void testGetCurrentMagnificationRegion_windowMode_exactRegionCenter() throws Exception {
1149         Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
1150 
1151         final MagnificationController controller = mService.getMagnificationController();
1152         final Rect bounds = controller.getMagnificationRegion().getBounds();
1153         final float scale = 2.0f;
1154         final float x = bounds.centerX();
1155         final float y = bounds.centerY();
1156 
1157         final MagnificationConfig config = new MagnificationConfig.Builder()
1158                 .setMode(MAGNIFICATION_MODE_WINDOW)
1159                 .setScale(scale)
1160                 .setCenterX(x)
1161                 .setCenterY(y).build();
1162         try {
1163             mService.runOnServiceSync(() -> {
1164                 controller.setMagnificationConfig(config, false);
1165             });
1166             waitUntilMagnificationConfig(controller, config);
1167 
1168             final Region magnificationRegion = controller.getCurrentMagnificationRegion();
1169             final Rect magnificationBounds = magnificationRegion.getBounds();
1170             assertEquals(magnificationBounds.exactCenterX(), x, BOUNDS_TOLERANCE);
1171             assertEquals(magnificationBounds.exactCenterY(), y, BOUNDS_TOLERANCE);
1172         } finally {
1173             mService.runOnServiceSync(() -> {
1174                 controller.resetCurrentMagnification(false);
1175             });
1176         }
1177     }
1178 
1179     @Test
testResetCurrentMagnificationRegion_WindowMode_regionIsEmpty()1180     public void testResetCurrentMagnificationRegion_WindowMode_regionIsEmpty() throws Exception {
1181         Assume.assumeTrue(isWindowModeSupported(mInstrumentation.getContext()));
1182 
1183         final MagnificationController controller = mService.getMagnificationController();
1184         final Rect bounds = controller.getMagnificationRegion().getBounds();
1185         final float scale = 2.0f;
1186         final float x = bounds.centerX();
1187         final float y = bounds.centerY();
1188 
1189         final MagnificationConfig config = new MagnificationConfig.Builder()
1190                 .setMode(MAGNIFICATION_MODE_WINDOW)
1191                 .setScale(scale)
1192                 .setCenterX(x)
1193                 .setCenterY(y).build();
1194 
1195         mService.runOnServiceSync(() -> {
1196             controller.setMagnificationConfig(config, false);
1197         });
1198         waitUntilMagnificationConfig(controller, config);
1199 
1200         assertEquals(scale, controller.getMagnificationConfig().getScale(), 0);
1201 
1202         mService.runOnServiceSync(() -> {
1203             controller.resetCurrentMagnification(false);
1204         });
1205 
1206         assertFalse(controller.getMagnificationConfig().isActivated());
1207         assertEquals(1.0f, controller.getMagnificationConfig().getScale(), 0);
1208         assertTrue(controller.getCurrentMagnificationRegion().isEmpty());
1209     }
1210 
1211     @Test
testAnimatingMagnification()1212     public void testAnimatingMagnification() throws InterruptedException {
1213         final MagnificationController controller = mService.getMagnificationController();
1214         final int timeBetweenAnimationChanges = 100;
1215 
1216         final float scale1 = 5.0f;
1217         final float x1 = 500;
1218         final float y1 = 1000;
1219 
1220         final float scale2 = 4.0f;
1221         final float x2 = 500;
1222         final float y2 = 1500;
1223 
1224         final float scale3 = 2.1f;
1225         final float x3 = 700;
1226         final float y3 = 700;
1227 
1228         for (int i = 0; i < 5; i++) {
1229             mService.runOnServiceSync(() -> {
1230                 controller.setScale(scale1, true);
1231                 controller.setCenter(x1, y1, true);
1232             });
1233 
1234             Thread.sleep(timeBetweenAnimationChanges);
1235 
1236             mService.runOnServiceSync(() -> {
1237                 controller.setScale(scale2, true);
1238                 controller.setCenter(x2, y2, true);
1239             });
1240 
1241             Thread.sleep(timeBetweenAnimationChanges);
1242 
1243             mService.runOnServiceSync(() -> {
1244                 controller.setScale(scale3, true);
1245                 controller.setCenter(x3, y3, true);
1246             });
1247 
1248             Thread.sleep(timeBetweenAnimationChanges);
1249         }
1250 
1251         mService.runOnServiceSync(() -> {
1252             controller.resetCurrentMagnification(/* animate= */ false);
1253         });
1254     }
1255 
1256     @Test
1257     @FlakyTest
testA11yNodeInfoVisibility_whenOutOfMagnifiedArea_shouldVisible()1258     public void testA11yNodeInfoVisibility_whenOutOfMagnifiedArea_shouldVisible()
1259             throws Exception{
1260         Activity activity = launchActivityAndWaitForItToBeOnscreen(
1261                 mInstrumentation, sUiAutomation, mActivityRule);
1262         final MagnificationController controller = mService.getMagnificationController();
1263         final Rect magnifyBounds = controller.getMagnificationRegion().getBounds();
1264         final float scale = 8.0f;
1265         final Button button = activity.findViewById(R.id.button1);
1266         adjustViewBoundsIfNeeded(button, scale, magnifyBounds);
1267 
1268         final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow()
1269                 .findAccessibilityNodeInfosByViewId(
1270                         "android.accessibilityservice.cts:id/button1").get(0);
1271         assertNotNull("Can't find button on the screen", buttonNode);
1272         assertTrue("Button should be visible", buttonNode.isVisibleToUser());
1273 
1274         // Get right-bottom center position
1275         final float centerX = magnifyBounds.left + (((float) magnifyBounds.width() / (2.0f * scale))
1276                 * ((2.0f * scale) - 1.0f));
1277         final float centerY = magnifyBounds.top + (((float) magnifyBounds.height() / (2.0f * scale))
1278                 * ((2.0f * scale) - 1.0f));
1279         final Rect boundsBeforeMagnify = new Rect();
1280         buttonNode.getBoundsInScreen(boundsBeforeMagnify);
1281         final Rect boundsAfterMagnify = new Rect();
1282         try {
1283             waitOnMagnificationChanged(controller, scale, centerX, centerY);
1284 
1285             final DisplayMetrics displayMetrics = new DisplayMetrics();
1286             activity.getDisplay().getMetrics(displayMetrics);
1287             final Rect displayRect = new Rect(0, 0,
1288                     displayMetrics.widthPixels, displayMetrics.heightPixels);
1289 
1290             // The boundsInScreen of button is adjusted to outside of screen by framework,
1291             // for example, Rect(-xxx, -xxx, -xxx, -xxx). Intersection of button and screen
1292             // should be empty.
1293             TestUtils.waitUntil("Button shouldn't be on the screen, screen is " + displayRect
1294                     + ", button bounds before magnified is " + boundsBeforeMagnify
1295                     + ", button bounds after layout is " + boundsAfterMagnify,
1296                     TIMEOUT_CONFIG_SECONDS,
1297                     () -> {
1298                         buttonNode.refresh();
1299                         buttonNode.getBoundsInScreen(boundsAfterMagnify);
1300                         return !Rect.intersects(displayRect, boundsAfterMagnify);
1301                     });
1302             assertTrue("Button should be visible", buttonNode.isVisibleToUser());
1303         } finally {
1304             mService.runOnServiceSync(() -> controller.reset(false));
1305         }
1306     }
1307 
1308     @Test
testShowMagnifiableWindow_outOfTheMagnifiedRegion_moveMagnification()1309     public void testShowMagnifiableWindow_outOfTheMagnifiedRegion_moveMagnification()
1310             throws Exception {
1311         Activity activity = launchActivityAndWaitForItToBeOnscreen(
1312                 mInstrumentation, sUiAutomation, mActivityRule);
1313 
1314         final MagnificationController controller = mService.getMagnificationController();
1315         final Rect magnifyBounds = controller.getMagnificationRegion().getBounds();
1316         final float scale = 4.0f;
1317         // Get right-bottom center position
1318         final float centerX = magnifyBounds.left + (((float) magnifyBounds.width() / (2.0f * scale))
1319                 * ((2.0f * scale) - 1.0f));
1320         final float centerY = magnifyBounds.top + (((float) magnifyBounds.height() / (2.0f * scale))
1321                 * ((2.0f * scale) - 1.0f));
1322 
1323         Button createdButton = null;
1324 
1325         try {
1326             waitOnMagnificationChanged(controller, scale, centerX, centerY);
1327 
1328             // Add window at left-top position
1329             Button button = addMagnifiableWindowInActivity(R.string.button1, params -> {
1330                 params.width = magnifyBounds.width() / 4;
1331                 params.height = magnifyBounds.height() / 4;
1332                 params.gravity = Gravity.TOP | Gravity.LEFT;
1333             }, activity);
1334             createdButton = button;
1335 
1336             TestUtils.waitUntil("bounds are not intersected:", TIMEOUT_CONFIG_SECONDS,
1337                     () -> {
1338                         Rect magnifiedArea = getMagnifiedArea(controller);
1339                         Rect buttonBounds = new Rect();
1340                         button.getBoundsOnScreen(buttonBounds, false);
1341                         // magnification should be moved to make the button visible on screen
1342                         return magnifiedArea.intersect(buttonBounds);
1343                     });
1344 
1345         } finally {
1346             if (createdButton != null) {
1347                 activity.getWindowManager().removeView(createdButton);
1348             }
1349             mService.runOnServiceSync(() -> controller.reset(false));
1350         }
1351     }
1352 
1353     @Test
testShowNonMagnifiableWindow_outOfTheMagnifiedRegion_shouldNotMoveMagnification()1354     public void testShowNonMagnifiableWindow_outOfTheMagnifiedRegion_shouldNotMoveMagnification()
1355             throws Exception {
1356         final MagnificationController controller = mService.getMagnificationController();
1357         final Rect magnifyBounds = controller.getMagnificationRegion().getBounds();
1358         final float scale = 4.0f;
1359         // Get right-bottom center position
1360         final float centerX = magnifyBounds.left + (((float) magnifyBounds.width() / (2.0f * scale))
1361                 * ((2.0f * scale) - 1.0f));
1362         final float centerY = magnifyBounds.top + (((float) magnifyBounds.height() / (2.0f * scale))
1363                 * ((2.0f * scale) - 1.0f));
1364 
1365         Button createdButton = null;
1366 
1367         try {
1368             waitOnMagnificationChanged(controller, scale, centerX, centerY);
1369 
1370             // Add window at left-top position
1371             Button button = addNonMagnifiableWindow(R.string.button1, "button1", params -> {
1372                 params.width = magnifyBounds.width() / 4;
1373                 params.height = magnifyBounds.height() / 4;
1374                 params.gravity = Gravity.TOP | Gravity.LEFT;
1375             });
1376             createdButton = button;
1377             // wait 1 second to check if the magnification would move.
1378             SystemClock.sleep(1000);
1379             Rect magnifiedArea = getMagnifiedArea(controller);
1380             Rect buttonBounds = new Rect();
1381             button.getBoundsOnScreen(buttonBounds, false);
1382             // magnification should not be moved since the button is already visible on screen
1383             assertFalse(magnifiedArea.intersect(buttonBounds));
1384             assertEquals(centerX, controller.getCenterX(), 0f);
1385             assertEquals(centerY, controller.getCenterY(), 0f);
1386         } finally {
1387             if (createdButton != null) {
1388                 mInstrumentation.getContext().getSystemService(WindowManager.class).removeView(
1389                         createdButton);
1390             }
1391             mService.runOnServiceSync(() -> controller.reset(false));
1392         }
1393     }
1394 
isMagnificationOverlayExisting()1395     private boolean isMagnificationOverlayExisting() {
1396         return sUiAutomation.getWindows().stream().anyMatch(
1397                 accessibilityWindowInfo -> accessibilityWindowInfo.getType()
1398                         == AccessibilityWindowInfo.TYPE_MAGNIFICATION_OVERLAY);
1399     }
1400 
doesAccessibilityMagnificationOverlayExist()1401     private boolean doesAccessibilityMagnificationOverlayExist() {
1402         return sUiAutomation.getWindows().stream().anyMatch(
1403                 // TODO: b/335440685 - Move to TYPE_ACCESSIBILITY_OVERLAY after the issues with
1404                 // that type preventing swipe to navigate are resolved.
1405                 accessibilityWindowInfo -> accessibilityWindowInfo.getType()
1406                         == AccessibilityWindowInfo.TYPE_MAGNIFICATION_OVERLAY);
1407     }
1408 
getMagnifiedArea(MagnificationController magnificationController)1409     private Rect getMagnifiedArea(MagnificationController magnificationController) {
1410         final Rect magnifyBounds = magnificationController.getMagnificationRegion().getBounds();
1411         final float scale = magnificationController.getScale();
1412         final float centerX = magnificationController.getCenterX();
1413         final float centerY = magnificationController.getCenterX();
1414 
1415         float halfWidth = magnifyBounds.width() / (2.0f * scale);
1416         float halfHeight = magnifyBounds.height() / (2.0f * scale);
1417         return new Rect(
1418                 (int) (centerX - halfWidth),
1419                 (int) (centerY - halfHeight),
1420                 (int) (centerX + halfWidth),
1421                 (int) (centerY + halfHeight));
1422     }
1423 
addNonMagnifiableWindow(int btnTextRes, String title, Consumer<WindowManager.LayoutParams> configure)1424     private Button addNonMagnifiableWindow(int btnTextRes, String title,
1425             Consumer<WindowManager.LayoutParams> configure) throws Exception {
1426         final WindowManager wm =
1427                 mInstrumentation.getContext().getSystemService(WindowManager.class);
1428         AtomicReference<Button> result = new AtomicReference<>();
1429 
1430         sUiAutomation.executeAndWaitForEvent(() -> {
1431             mInstrumentation.runOnMainSync(() -> {
1432                 final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
1433                 params.width = WindowManager.LayoutParams.MATCH_PARENT;
1434                 params.height = WindowManager.LayoutParams.WRAP_CONTENT;
1435                 params.type = TYPE_NAVIGATION_BAR_PANEL;
1436                 params.accessibilityTitle = title;
1437                 params.format = PixelFormat.TRANSPARENT;
1438                 configure.accept(params);
1439 
1440                 final Button button = new Button(mInstrumentation.getContext());
1441                 button.setText(btnTextRes);
1442                 result.set(button);
1443                 SystemUtil.runWithShellPermissionIdentity(sUiAutomation,
1444                         () -> wm.addView(button, params),
1445                         "android.permission.INTERNAL_SYSTEM_WINDOW",
1446                         "android.permission.STATUS_BAR_SERVICE");
1447             });
1448         }, filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED), DEFAULT_TIMEOUT_MS);
1449         return result.get();
1450     }
1451 
addMagnifiableWindowInActivity(int btnTextRes, Consumer<WindowManager.LayoutParams> configure, Activity activity)1452     private Button addMagnifiableWindowInActivity(int btnTextRes,
1453             Consumer<WindowManager.LayoutParams> configure,
1454             Activity activity) throws TimeoutException {
1455         AtomicReference<Button> result = new AtomicReference<>();
1456         sUiAutomation.executeAndWaitForEvent(() -> {
1457             mInstrumentation.runOnMainSync(() -> {
1458                 final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
1459                 params.width = WindowManager.LayoutParams.MATCH_PARENT;
1460                 params.height = WindowManager.LayoutParams.WRAP_CONTENT;
1461                 params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
1462                         | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
1463                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1464                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
1465                 params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
1466                 params.token = activity.getWindow().getDecorView().getWindowToken();
1467                 configure.accept(params);
1468 
1469                 final Button button = new Button(activity);
1470                 button.setText(btnTextRes);
1471                 result.set(button);
1472                 activity.getWindowManager().addView(button, params);
1473             });
1474         }, filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED), DEFAULT_TIMEOUT_MS);
1475         return result.get();
1476     }
1477 
waitOnMagnificationChanged(MagnificationController controller, float newScale, float newCenterX, float newCenterY)1478     private void waitOnMagnificationChanged(MagnificationController controller, float newScale,
1479             float newCenterX, float newCenterY) {
1480         final OnMagnificationChangedListener spyListener = mock(
1481                 OnMagnificationChangedListener.class);
1482         final OnMagnificationChangedListener listener =
1483                 (controller1, region, scale, centerX, centerY) ->
1484                         spyListener.onMagnificationChanged(controller1, region, scale, centerX,
1485                                 centerY);
1486         controller.addListener(listener);
1487         try {
1488             final AtomicBoolean setScale = new AtomicBoolean();
1489             final AtomicBoolean setCenter = new AtomicBoolean();
1490             mService.runOnServiceSync(() -> {
1491                 setScale.set(controller.setScale(newScale, false));
1492             });
1493 
1494             assertTrue(getSetterErrorMessage("Failed to set scale"), setScale.get());
1495             verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
1496                     eq(controller), any(Region.class), eq(newScale), anyFloat(), anyFloat());
1497 
1498             reset(spyListener);
1499             mService.runOnServiceSync(() -> {
1500                 setCenter.set(controller.setCenter(newCenterX, newCenterY, false));
1501             });
1502 
1503             assertTrue(getSetterErrorMessage("Failed to set center"), setCenter.get());
1504             verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
1505                     eq(controller), any(Region.class), anyFloat(), eq(newCenterX), eq(newCenterY));
1506         } finally {
1507             controller.removeListener(listener);
1508         }
1509     }
1510 
1511     /**
1512      * Adjust top-left view bounds if it's still in the magnified viewport after sets magnification
1513      * scale and move centers to bottom-right.
1514      */
adjustViewBoundsIfNeeded(View topLeftview, float scale, Rect magnifyBounds)1515     private void adjustViewBoundsIfNeeded(View topLeftview, float scale, Rect magnifyBounds) {
1516         final Point magnifyViewportTopLeft = new Point();
1517         magnifyViewportTopLeft.x = (int)((scale - 1.0f) * ((float) magnifyBounds.width() / scale));
1518         magnifyViewportTopLeft.y = (int)((scale - 1.0f) * ((float) magnifyBounds.height() / scale));
1519         magnifyViewportTopLeft.offset(magnifyBounds.left, magnifyBounds.top);
1520 
1521         final int[] viewLocation = new int[2];
1522         topLeftview.getLocationOnScreen(viewLocation);
1523         final Rect viewBounds = new Rect(viewLocation[0], viewLocation[1],
1524                 viewLocation[0] + topLeftview.getWidth(),
1525                 viewLocation[1] + topLeftview.getHeight());
1526         if (viewBounds.right < magnifyViewportTopLeft.x
1527                 && viewBounds.bottom < magnifyViewportTopLeft.y) {
1528             // no need
1529             return;
1530         }
1531 
1532         final ViewGroup.LayoutParams layoutParams = topLeftview.getLayoutParams();
1533         if (viewBounds.right >= magnifyViewportTopLeft.x) {
1534             layoutParams.width = topLeftview.getWidth() - 1
1535                     - (viewBounds.right - magnifyViewportTopLeft.x);
1536             assertTrue("Needs to fix layout", layoutParams.width > 0);
1537         }
1538         if (viewBounds.bottom >= magnifyViewportTopLeft.y) {
1539             layoutParams.height = topLeftview.getHeight() - 1
1540                     - (viewBounds.bottom - magnifyViewportTopLeft.y);
1541             assertTrue("Needs to fix layout", layoutParams.height > 0);
1542         }
1543         mInstrumentation.runOnMainSync(() -> topLeftview.setLayoutParams(layoutParams));
1544         // Waiting for UI refresh
1545         mInstrumentation.waitForIdleSync();
1546     }
1547 
waitUntilMagnificationConfig(MagnificationController controller, MagnificationConfig config)1548     private void waitUntilMagnificationConfig(MagnificationController controller,
1549             MagnificationConfig config) throws Exception {
1550         TestUtils.waitUntil(
1551                 "Failed to apply the config. expected: " + config + " , actual: "
1552                         + controller.getMagnificationConfig(), TIMEOUT_CONFIG_SECONDS,
1553                 () -> {
1554                     final MagnificationConfig actualConfig = controller.getMagnificationConfig();
1555                     Log.d(TAG, "Polling config: " + actualConfig.toString());
1556                     // If expected config activated is false, we just need to verify the activated
1557                     // value is the same. Otherwise, we need to check all the actual values are
1558                     // equal to the expected values.
1559                     if (config.isActivated()) {
1560                         return actualConfig.getMode() == config.getMode()
1561                                 && actualConfig.isActivated() == config.isActivated()
1562                                 && Float.compare(actualConfig.getScale(), config.getScale()) == 0
1563                                 && (Math.abs(actualConfig.getCenterX() - config.getCenterX())
1564                                         <= BOUNDS_TOLERANCE)
1565                                 && (Math.abs(actualConfig.getCenterY() - config.getCenterY())
1566                                         <= BOUNDS_TOLERANCE);
1567                     } else {
1568                         return actualConfig.isActivated() == config.isActivated();
1569                     }
1570                 });
1571     }
1572 
waitUntilMagnificationRegion(MagnificationController controller, Region expectedRegion)1573     private void waitUntilMagnificationRegion(MagnificationController controller,
1574             Region expectedRegion) throws Exception {
1575         TestUtils.waitUntil(
1576                 "Failed to apply the region. expected: " + expectedRegion + " , actual: "
1577                         + controller.getMagnificationRegion(), TIMEOUT_CONFIG_SECONDS,
1578                 () -> {
1579                     final Region actualRegion = controller.getMagnificationRegion();
1580                     Log.d(TAG, "Polling region: " + actualRegion);
1581                     return actualRegion.equals(expectedRegion);
1582                 });
1583     }
1584 
assertConfigEquals(MagnificationConfig expected, MagnificationConfig result)1585     private void assertConfigEquals(MagnificationConfig expected, MagnificationConfig result) {
1586         // If expected config activated is false, we just need to verify the activated
1587         // value is the same. Otherwise, we need to check all the actual values are
1588         // equal to the expected values.
1589         if (expected.isActivated()) {
1590             assertEquals("Failed to apply mode", expected.getMode(),
1591                     result.getMode(), 0f);
1592             assertEquals("Failed to apply activated", expected.isActivated(),
1593                     result.isActivated());
1594             assertEquals("Failed to apply scale", expected.getScale(),
1595                     result.getScale(), 0f);
1596             assertEquals("Failed to apply center X", expected.getCenterX(),
1597                     result.getCenterX(), 5.0f);
1598             assertEquals("Failed to apply center Y", expected.getCenterY(),
1599                     result.getCenterY(), 5.0f);
1600         } else {
1601             assertEquals("Failed to apply activated", expected.isActivated(),
1602                     result.isActivated());
1603         }
1604     }
1605 
isWindowModeSupported(Context context)1606     private static boolean isWindowModeSupported(Context context) {
1607         PackageManager pm = context.getPackageManager();
1608         final boolean isWatch = pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
1609         return pm.hasSystemFeature(FEATURE_WINDOW_MAGNIFICATION) && !isWatch;
1610     }
1611 
obtainConfigBuilder(MagnificationConfig config)1612     private static MagnificationConfig.Builder obtainConfigBuilder(MagnificationConfig config) {
1613         MagnificationConfig.Builder builder = new MagnificationConfig.Builder();
1614         builder.setMode(config.getMode())
1615                 .setScale(config.getScale())
1616                 .setCenterX(config.getCenterX())
1617                 .setCenterY(config.getCenterY());
1618         return builder;
1619     }
1620 
getSetterErrorMessage(String rawMessage)1621     private String getSetterErrorMessage(String rawMessage) {
1622         if (Flags.alwaysDrawMagnificationFullscreenBorder()) {
1623             String postfix = ". The failure may be because the service connection does not exist"
1624                     + ", the given setter info does not make the magnification spec changed"
1625                     + ", or the SystemUI connection does not exist."
1626                     + " Please ensure your SystemUI implements the IMagnificationConnection AIDL.";
1627             return rawMessage + postfix;
1628         } else {
1629             return rawMessage;
1630         }
1631     }
1632 }
1633